From 197c936cd80096144be1274cb7d72c67be33edf7 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:04:20 +0500 Subject: [PATCH 01/16] test: define durable query parity --- test/application/durable-query-parity.test.ts | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 test/application/durable-query-parity.test.ts diff --git a/test/application/durable-query-parity.test.ts b/test/application/durable-query-parity.test.ts new file mode 100644 index 0000000..d5ae133 --- /dev/null +++ b/test/application/durable-query-parity.test.ts @@ -0,0 +1,318 @@ +import { mkdtempSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import type { Agent, Checkpoint, Goal, Lease, Session, Task } from '@mindrail/contracts'; +import { describe, expect, it } from 'vitest'; + +import { createDurableApplicationDispatcher } from '../../src/application/durable-dispatcher.ts'; +import type { ApplicationDispatcher } from '../../src/application/ports.ts'; +import type { DurableRuntimePersistence } from '../../src/persistence/ports.ts'; +import { workspace } from '../persistence/fixtures.ts'; +import { openPersistence } from '../persistence/setup.ts'; +import { canonicalDomainValidator } from '../runtime/canonical-domain-validator.ts'; + +function databasePath(): string { + return join(mkdtempSync(join(tmpdir(), 'mindrail-durable-query-parity-')), 'runtime.sqlite'); +} + +function dispatcherFor( + persistence: DurableRuntimePersistence, + prefix: string, +): ApplicationDispatcher { + let sequence = 0; + return createDurableApplicationDispatcher({ + persistence, + now: () => new Date('2026-08-29T18:00:00.000Z'), + idFactory: (kind) => `${prefix}-${kind}-${++sequence}`, + leaseDurationMs: 120_000, + sessionTimeoutMs: 60_000, + validateCanonicalDomainRecord: canonicalDomainValidator, + }); +} + +function successResult( + response: Awaited>, +): T { + expect('error' in response).toBe(false); + if ('error' in response) throw new Error(`Expected success, got ${response.error.code}.`); + return response.result as T; +} + +function queryResult(response: Awaited>): T { + expect('error' in response).toBe(false); + if ('error' in response) throw new Error(`Expected query success, got ${response.error.code}.`); + return response.result as T; +} + +function baseQuery(query: string) { + return { + protocolVersion: '0.1' as const, + query, + workspaceId: 'ws-a', + actor: { type: 'system' as const, id: 'system-1' }, + }; +} + +async function seed(dispatcher: ApplicationDispatcher) { + const actor = { type: 'system' as const, id: 'system-1' }; + const agent = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'RegisterAgent', + commandId: 'cmd-register', + workspaceId: 'ws-a', + actor, + displayName: 'Query parity worker', + capabilities: ['repo.write'], + }), + ); + const session = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'StartSession', + commandId: 'cmd-session', + workspaceId: 'ws-a', + actor, + agentId: agent.id, + }), + ); + const firstGoal = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'CreateGoal', + commandId: 'cmd-goal-1', + workspaceId: 'ws-a', + actor, + title: 'First goal', + objective: 'Exercise durable goal reads.', + successCriteria: ['Goals are paged deterministically.'], + }), + ); + const secondGoal = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'CreateGoal', + commandId: 'cmd-goal-2', + workspaceId: 'ws-a', + actor, + title: 'Second goal', + objective: 'Provide a second durable goal.', + successCriteria: ['Second page contains this Goal.'], + }), + ); + const firstTask = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'CreateTask', + commandId: 'cmd-task-1', + workspaceId: 'ws-a', + actor, + goalId: firstGoal.id, + title: 'First task', + objective: 'Exercise execution projection.', + acceptanceCriteria: ['Latest execution state is queryable.'], + requiredCapabilities: ['repo.write'], + dependencyTaskIds: [], + }), + ); + const secondTask = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'CreateTask', + commandId: 'cmd-task-2', + workspaceId: 'ws-a', + actor, + goalId: firstGoal.id, + title: 'Second task', + objective: 'Provide a second Goal Task.', + acceptanceCriteria: ['Task pagination is deterministic.'], + requiredCapabilities: ['repo.write'], + dependencyTaskIds: [], + }), + ); + const claim = successResult<{ task: Task; lease: Lease }>( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'ClaimTask', + commandId: 'cmd-claim', + workspaceId: 'ws-a', + actor: { type: 'agent', id: agent.id }, + taskId: firstTask.id, + sessionId: session.id, + expectedTaskRevision: firstTask.revision, + }), + ); + const firstCheckpoint = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'RecordCheckpoint', + commandId: 'cmd-checkpoint-1', + workspaceId: 'ws-a', + actor: { type: 'agent', id: agent.id }, + taskId: claim.task.id, + sessionId: session.id, + leaseId: claim.lease.id, + fencingToken: claim.lease.fencingToken, + kind: 'progress', + summary: 'Earlier checkpoint.', + evidence: [], + progressPercent: 25, + }), + ); + const latestCheckpoint = successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'RecordCheckpoint', + commandId: 'cmd-checkpoint-2', + workspaceId: 'ws-a', + actor: { type: 'agent', id: agent.id }, + taskId: claim.task.id, + sessionId: session.id, + leaseId: claim.lease.id, + fencingToken: claim.lease.fencingToken, + kind: 'handoff', + summary: 'Latest checkpoint.', + evidence: [], + }), + ); + + return { + agent, + session, + firstGoal, + secondGoal, + firstTask: claim.task, + secondTask, + lease: claim.lease, + firstCheckpoint, + latestCheckpoint, + }; +} + +describe('durable query parity', () => { + it('paginates ListGoals and ListGoalTasks from authoritative D1 state after restart', async () => { + const path = databasePath(); + let opened = await openPersistence(path); + await opened.persistence.bootstrapWorkspace(workspace()); + const seeded = await seed(dispatcherFor(opened.persistence, 'seed')); + opened.database.close(); + + opened = await openPersistence(path); + const dispatcher = dispatcherFor(opened.persistence, 'fresh'); + + const firstGoals = queryResult<{ items: Goal[]; nextCursor?: string }>( + await dispatcher.dispatchQuery({ ...baseQuery('ListGoals'), limit: 1 } as never), + ); + expect(firstGoals).toEqual({ items: [seeded.firstGoal], nextCursor: 'c1' }); + const secondGoals = queryResult<{ items: Goal[]; nextCursor?: string }>( + await dispatcher.dispatchQuery({ + ...baseQuery('ListGoals'), + limit: 1, + cursor: firstGoals.nextCursor, + } as never), + ); + expect(secondGoals).toEqual({ items: [seeded.secondGoal] }); + + const firstTasks = queryResult<{ items: Task[]; nextCursor?: string }>( + await dispatcher.dispatchQuery({ + ...baseQuery('ListGoalTasks'), + goalId: seeded.firstGoal.id, + limit: 1, + } as never), + ); + expect(firstTasks).toEqual({ items: [seeded.firstTask], nextCursor: 'c1' }); + const secondTasks = queryResult<{ items: Task[]; nextCursor?: string }>( + await dispatcher.dispatchQuery({ + ...baseQuery('ListGoalTasks'), + goalId: seeded.firstGoal.id, + limit: 1, + cursor: firstTasks.nextCursor, + } as never), + ); + expect(secondTasks).toEqual({ items: [seeded.secondTask] }); + opened.database.close(); + }); + + it('returns a Task execution projection with only the effective Lease and latest Checkpoint', async () => { + const path = databasePath(); + let opened = await openPersistence(path); + await opened.persistence.bootstrapWorkspace(workspace()); + const seeded = await seed(dispatcherFor(opened.persistence, 'seed')); + opened.database.close(); + + opened = await openPersistence(path); + let dispatcher = dispatcherFor(opened.persistence, 'fresh'); + expect( + queryResult<{ + task: Task; + lease?: Lease; + latestCheckpoint?: Checkpoint; + }>( + await dispatcher.dispatchQuery({ + ...baseQuery('GetTaskExecutionView'), + taskId: seeded.firstTask.id, + } as never), + ), + ).toEqual({ + task: seeded.firstTask, + lease: seeded.lease, + latestCheckpoint: seeded.latestCheckpoint, + }); + + successResult( + await dispatcher.dispatchCommand({ + protocolVersion: '0.1', + command: 'ReleaseLease', + commandId: 'cmd-release', + workspaceId: 'ws-a', + actor: { type: 'agent', id: seeded.agent.id }, + sessionId: seeded.session.id, + leaseId: seeded.lease.id, + expectedLeaseRevision: seeded.lease.revision, + fencingToken: seeded.lease.fencingToken, + }), + ); + opened.database.close(); + + opened = await openPersistence(path); + dispatcher = dispatcherFor(opened.persistence, 'after-release'); + expect( + queryResult<{ + task: Task; + lease?: Lease; + latestCheckpoint?: Checkpoint; + }>( + await dispatcher.dispatchQuery({ + ...baseQuery('GetTaskExecutionView'), + taskId: seeded.firstTask.id, + } as never), + ), + ).toEqual({ + task: seeded.firstTask, + latestCheckpoint: seeded.latestCheckpoint, + }); + opened.database.close(); + }); + + it('returns bounded NOT_FOUND failures for missing ListGoalTasks parents and Task execution views', async () => { + const path = databasePath(); + const opened = await openPersistence(path); + await opened.persistence.bootstrapWorkspace(workspace()); + const dispatcher = dispatcherFor(opened.persistence, 'fresh'); + + const missingGoal = await dispatcher.dispatchQuery({ + ...baseQuery('ListGoalTasks'), + goalId: 'goal-missing', + limit: 10, + } as never); + expect(missingGoal).toMatchObject({ error: { code: 'NOT_FOUND', retryable: false } }); + + const missingTask = await dispatcher.dispatchQuery({ + ...baseQuery('GetTaskExecutionView'), + taskId: 'task-missing', + } as never); + expect(missingTask).toMatchObject({ error: { code: 'NOT_FOUND', retryable: false } }); + opened.database.close(); + }); +}); From 88ed52c69b9e7a648e2aeeef77225daacf97ddc8 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:05:25 +0500 Subject: [PATCH 02/16] chore: fix durable query RED fixture once --- .../fix-durable-query-red-fixture-once.yml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/fix-durable-query-red-fixture-once.yml diff --git a/.github/workflows/fix-durable-query-red-fixture-once.yml b/.github/workflows/fix-durable-query-red-fixture-once.yml new file mode 100644 index 0000000..44bd065 --- /dev/null +++ b/.github/workflows/fix-durable-query-red-fixture-once.yml @@ -0,0 +1,35 @@ +name: Fix durable query RED fixture once + +on: + push: + branches: + - feature/durable-query-parity + paths: + - .github/workflows/fix-durable-query-red-fixture-once.yml + +permissions: + contents: write + +jobs: + fix: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + - run: | + python3 - <<'PY' + from pathlib import Path + path = Path('test/application/durable-query-parity.test.ts') + text = path.read_text() + old = """ sessionId: seeded.session.id,\n leaseId: seeded.lease.id,\n expectedLeaseRevision: seeded.lease.revision,""" + new = """ sessionId: seeded.session.id,\n taskId: seeded.firstTask.id,\n leaseId: seeded.lease.id,\n expectedLeaseRevision: seeded.lease.revision,""" + if old not in text: + raise SystemExit('ReleaseLease fixture target not found') + path.write_text(text.replace(old, new, 1)) + PY + - run: git rm .github/workflows/fix-durable-query-red-fixture-once.yml + - run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add test/application/durable-query-parity.test.ts + git commit -m "test: fix durable query release fixture" + git push From c52318655a75dab44e677da2933b367c9ef4c153 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:05:32 +0000 Subject: [PATCH 03/16] test: fix durable query release fixture --- .../fix-durable-query-red-fixture-once.yml | 35 ------------------- test/application/durable-query-parity.test.ts | 1 + 2 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 .github/workflows/fix-durable-query-red-fixture-once.yml diff --git a/.github/workflows/fix-durable-query-red-fixture-once.yml b/.github/workflows/fix-durable-query-red-fixture-once.yml deleted file mode 100644 index 44bd065..0000000 --- a/.github/workflows/fix-durable-query-red-fixture-once.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Fix durable query RED fixture once - -on: - push: - branches: - - feature/durable-query-parity - paths: - - .github/workflows/fix-durable-query-red-fixture-once.yml - -permissions: - contents: write - -jobs: - fix: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - - run: | - python3 - <<'PY' - from pathlib import Path - path = Path('test/application/durable-query-parity.test.ts') - text = path.read_text() - old = """ sessionId: seeded.session.id,\n leaseId: seeded.lease.id,\n expectedLeaseRevision: seeded.lease.revision,""" - new = """ sessionId: seeded.session.id,\n taskId: seeded.firstTask.id,\n leaseId: seeded.lease.id,\n expectedLeaseRevision: seeded.lease.revision,""" - if old not in text: - raise SystemExit('ReleaseLease fixture target not found') - path.write_text(text.replace(old, new, 1)) - PY - - run: git rm .github/workflows/fix-durable-query-red-fixture-once.yml - - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add test/application/durable-query-parity.test.ts - git commit -m "test: fix durable query release fixture" - git push diff --git a/test/application/durable-query-parity.test.ts b/test/application/durable-query-parity.test.ts index d5ae133..3f95077 100644 --- a/test/application/durable-query-parity.test.ts +++ b/test/application/durable-query-parity.test.ts @@ -268,6 +268,7 @@ describe('durable query parity', () => { workspaceId: 'ws-a', actor: { type: 'agent', id: seeded.agent.id }, sessionId: seeded.session.id, + taskId: seeded.firstTask.id, leaseId: seeded.lease.id, expectedLeaseRevision: seeded.lease.revision, fencingToken: seeded.lease.fencingToken, From 70a058f061aec59afa1cfd479f2e84cff112e0d1 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:06:02 +0500 Subject: [PATCH 04/16] chore: trigger durable query RED quality From c5daed05ae25ac0bcdb0dd7c704e18d1b6cc389c Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:07:47 +0500 Subject: [PATCH 05/16] chore: stage durable query parity implementation --- scripts/implement-durable-query-parity.py | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 scripts/implement-durable-query-parity.py diff --git a/scripts/implement-durable-query-parity.py b/scripts/implement-durable-query-parity.py new file mode 100644 index 0000000..e7425d8 --- /dev/null +++ b/scripts/implement-durable-query-parity.py @@ -0,0 +1,46 @@ +from pathlib import Path + + +def replace_once(path: str, old: str, new: str) -> None: + file = Path(path) + text = file.read_text() + if old not in text: + raise SystemExit(f'target not found in {path}: {old[:80]!r}') + file.write_text(text.replace(old, new, 1)) + + +replace_once( + 'src/persistence/ports.ts', + '''export interface PendingHumanPermission {\n request: PermissionRequest;\n latestDecision: PermissionDecision;\n}\n''', + '''export interface PendingHumanPermission {\n request: PermissionRequest;\n latestDecision: PermissionDecision;\n}\n\nexport interface TaskExecutionView {\n task: Task;\n lease?: Lease;\n latestCheckpoint?: Checkpoint;\n}\n''', +) + +replace_once( + 'src/persistence/ports.ts', + ''' getPermissionRequest(\n workspaceId: string,\n requestId: string,\n ): Promise;\n loadWorkspaceState(workspaceId: string): Promise;\n''', + ''' getPermissionRequest(\n workspaceId: string,\n requestId: string,\n ): Promise;\n listGoals(workspaceId: string, limit: number, offset?: number): Promise;\n listGoalTasks(\n workspaceId: string,\n goalId: string,\n limit?: number,\n offset?: number,\n ): Promise;\n getTaskExecutionView(\n workspaceId: string,\n taskId: string,\n now: string,\n sessionCutoff: string,\n ): Promise;\n loadWorkspaceState(workspaceId: string): Promise;\n''', +) + +replace_once( + 'src/persistence/cloudflare/d1-runtime-persistence.ts', + ''' type StoredCommandReceipt,\n type TaskOutcomeCommitInput,\n''', + ''' type StoredCommandReceipt,\n type TaskExecutionView,\n type TaskOutcomeCommitInput,\n''', +) + +replace_once( + 'src/persistence/cloudflare/d1-runtime-persistence.ts', + ''' async loadWorkspaceState(workspaceId: string): Promise {\n''', + ''' async listGoals(workspaceId: string, limit: number, offset = 0): Promise {\n return this.readRecords(\n `SELECT record_json FROM goals\n WHERE workspace_id = ? ORDER BY created_at_ms, id LIMIT ? OFFSET ?`,\n workspaceId,\n boundedLimit(limit),\n boundedOffset(offset),\n );\n }\n\n async getTaskExecutionView(\n workspaceId: string,\n taskId: string,\n now: string,\n sessionCutoff: string,\n ): Promise {\n const task = await this.getTask(workspaceId, taskId);\n if (!task) return undefined;\n const nowMs = timestampMs(now, 'Task execution view now');\n const cutoffMs = timestampMs(sessionCutoff, 'Task execution view session cutoff');\n const lease =\n task.status === 'running'\n ? await this.getEffectiveActiveLease(workspaceId, taskId, nowMs, cutoffMs)\n : undefined;\n const latestCheckpoint = await this.readRecord(\n `SELECT record_json FROM checkpoints\n WHERE workspace_id = ? AND task_id = ?\n ORDER BY created_at_ms DESC, id DESC LIMIT 1`,\n workspaceId,\n taskId,\n );\n return {\n task: clone(task),\n ...(lease === undefined ? {} : { lease: clone(lease) }),\n ...(latestCheckpoint === undefined\n ? {}\n : { latestCheckpoint: clone(latestCheckpoint) }),\n };\n }\n\n async loadWorkspaceState(workspaceId: string): Promise {\n''', +) + +replace_once( + 'src/persistence/cloudflare/d1-runtime-persistence.ts', + ''' private async listGoalTasks(workspaceId: string, goalId: string): Promise {\n return this.readRecords(\n `SELECT record_json FROM tasks\n WHERE workspace_id = ? AND goal_id = ? ORDER BY created_at_ms, id`,\n workspaceId,\n goalId,\n );\n }\n''', + ''' async listGoalTasks(\n workspaceId: string,\n goalId: string,\n limit?: number,\n offset = 0,\n ): Promise {\n if (limit === undefined) {\n return this.readRecords(\n `SELECT record_json FROM tasks\n WHERE workspace_id = ? AND goal_id = ? ORDER BY created_at_ms, id`,\n workspaceId,\n goalId,\n );\n }\n return this.readRecords(\n `SELECT record_json FROM tasks\n WHERE workspace_id = ? AND goal_id = ?\n ORDER BY created_at_ms, id LIMIT ? OFFSET ?`,\n workspaceId,\n goalId,\n boundedLimit(limit),\n boundedOffset(offset),\n );\n }\n''', +) + +replace_once( + 'src/application/durable-dispatcher.ts', + ''' case 'ListGoals':\n case 'ListGoalTasks':\n case 'GetTaskExecutionView':\n return unsupportedQuery(query);\n''', + ''' case 'ListGoals': {\n if (!(await options.persistence.getWorkspace(query.workspaceId))) {\n return queryFailure(query, 'NOT_FOUND', 'Durable Workspace was not found.');\n }\n const window = listWindow(query.limit, query.cursor);\n if (!window) return invalidListWindow(query);\n const rows = await options.persistence.listGoals(\n query.workspaceId,\n window.limit + 1,\n window.offset,\n );\n return querySuccess(query, pageRows(rows, window.limit, window.offset));\n }\n case 'ListGoalTasks': {\n if (!(await options.persistence.getGoal(query.workspaceId, query.goalId))) {\n return queryFailure(query, 'NOT_FOUND', 'Durable Goal was not found.');\n }\n const window = listWindow(query.limit, query.cursor);\n if (!window) return invalidListWindow(query);\n const rows = await options.persistence.listGoalTasks(\n query.workspaceId,\n query.goalId,\n window.limit + 1,\n window.offset,\n );\n return querySuccess(query, pageRows(rows, window.limit, window.offset));\n }\n case 'GetTaskExecutionView': {\n const now = options.now();\n return durableResourceQuery(\n query,\n await options.persistence.getTaskExecutionView(\n query.workspaceId,\n query.taskId,\n now.toISOString(),\n new Date(now.getTime() - options.sessionTimeoutMs).toISOString(),\n ),\n );\n }\n''', +) From e4e3a389a59f50722c190be213e0bc0d50e61bd0 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:07:59 +0500 Subject: [PATCH 06/16] chore: run durable query parity implementation once --- .../implement-durable-query-parity-once.yml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/implement-durable-query-parity-once.yml diff --git a/.github/workflows/implement-durable-query-parity-once.yml b/.github/workflows/implement-durable-query-parity-once.yml new file mode 100644 index 0000000..cca1158 --- /dev/null +++ b/.github/workflows/implement-durable-query-parity-once.yml @@ -0,0 +1,35 @@ +name: Implement durable query parity once + +on: + push: + branches: + - feature/durable-query-parity + paths: + - .github/workflows/implement-durable-query-parity-once.yml + +permissions: + contents: write + +jobs: + implement: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + with: + node-version-file: package.json + package-manager-cache: false + - run: npm install --global "$(node -p "require('./package.json').packageManager")" + - run: pnpm install --frozen-lockfile + - run: python3 scripts/implement-durable-query-parity.py + - run: pnpm exec prettier --write src/persistence/ports.ts src/persistence/cloudflare/d1-runtime-persistence.ts src/application/durable-dispatcher.ts test/application/durable-query-parity.test.ts + - run: git rm .github/workflows/implement-durable-query-parity-once.yml scripts/implement-durable-query-parity.py + - run: pnpm vitest run test/application/durable-query-parity.test.ts test/application/durable-queries.test.ts + - run: pnpm check + - run: pnpm test:coverage + - run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add src/persistence/ports.ts src/persistence/cloudflare/d1-runtime-persistence.ts src/application/durable-dispatcher.ts test/application/durable-query-parity.test.ts + git commit -m "feat: complete durable query parity" + git push From 2bcd8086c9e8cc71b313b8f438c678a473ca8dc1 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:09:16 +0500 Subject: [PATCH 07/16] chore: remove obsolete durable query helper --- scripts/implement-durable-query-parity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/implement-durable-query-parity.py b/scripts/implement-durable-query-parity.py index e7425d8..63ff59b 100644 --- a/scripts/implement-durable-query-parity.py +++ b/scripts/implement-durable-query-parity.py @@ -44,3 +44,9 @@ def replace_once(path: str, old: str, new: str) -> None: ''' case 'ListGoals':\n case 'ListGoalTasks':\n case 'GetTaskExecutionView':\n return unsupportedQuery(query);\n''', ''' case 'ListGoals': {\n if (!(await options.persistence.getWorkspace(query.workspaceId))) {\n return queryFailure(query, 'NOT_FOUND', 'Durable Workspace was not found.');\n }\n const window = listWindow(query.limit, query.cursor);\n if (!window) return invalidListWindow(query);\n const rows = await options.persistence.listGoals(\n query.workspaceId,\n window.limit + 1,\n window.offset,\n );\n return querySuccess(query, pageRows(rows, window.limit, window.offset));\n }\n case 'ListGoalTasks': {\n if (!(await options.persistence.getGoal(query.workspaceId, query.goalId))) {\n return queryFailure(query, 'NOT_FOUND', 'Durable Goal was not found.');\n }\n const window = listWindow(query.limit, query.cursor);\n if (!window) return invalidListWindow(query);\n const rows = await options.persistence.listGoalTasks(\n query.workspaceId,\n query.goalId,\n window.limit + 1,\n window.offset,\n );\n return querySuccess(query, pageRows(rows, window.limit, window.offset));\n }\n case 'GetTaskExecutionView': {\n const now = options.now();\n return durableResourceQuery(\n query,\n await options.persistence.getTaskExecutionView(\n query.workspaceId,\n query.taskId,\n now.toISOString(),\n new Date(now.getTime() - options.sessionTimeoutMs).toISOString(),\n ),\n );\n }\n''', ) + +replace_once( + 'src/application/durable-dispatcher.ts', + '''function unsupportedQuery(query: ApplicationQuery): QueryFailure {\n return queryFailure(\n query,\n 'UNSUPPORTED_OPERATION',\n `${query.query} is not yet integrated in the durable application composition.`,\n );\n}\n\n''', + '', +) From 503b0eb5189672a97f89b29884c6ec056c04369a Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:09:36 +0500 Subject: [PATCH 08/16] chore: rerun durable query parity implementation From 63a884740600a402ddda0b7cf46bc5032a3359a0 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:10:02 +0500 Subject: [PATCH 09/16] chore: retrigger durable query parity workflow --- .github/workflows/implement-durable-query-parity-once.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/implement-durable-query-parity-once.yml b/.github/workflows/implement-durable-query-parity-once.yml index cca1158..85bf92e 100644 --- a/.github/workflows/implement-durable-query-parity-once.yml +++ b/.github/workflows/implement-durable-query-parity-once.yml @@ -1,4 +1,5 @@ name: Implement durable query parity once +# rerun after removing obsolete unsupportedQuery helper on: push: From 1d81e59710a737fc9bff5e0ab1cd88fcc42c4878 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:10:46 +0000 Subject: [PATCH 10/16] feat: complete durable query parity --- .../implement-durable-query-parity-once.yml | 36 ----------- scripts/implement-durable-query-parity.py | 52 ---------------- src/application/durable-dispatcher.ts | 51 ++++++++++++---- .../cloudflare/d1-runtime-persistence.ts | 59 ++++++++++++++++++- src/persistence/ports.ts | 19 ++++++ 5 files changed, 115 insertions(+), 102 deletions(-) delete mode 100644 .github/workflows/implement-durable-query-parity-once.yml delete mode 100644 scripts/implement-durable-query-parity.py diff --git a/.github/workflows/implement-durable-query-parity-once.yml b/.github/workflows/implement-durable-query-parity-once.yml deleted file mode 100644 index 85bf92e..0000000 --- a/.github/workflows/implement-durable-query-parity-once.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Implement durable query parity once -# rerun after removing obsolete unsupportedQuery helper - -on: - push: - branches: - - feature/durable-query-parity - paths: - - .github/workflows/implement-durable-query-parity-once.yml - -permissions: - contents: write - -jobs: - implement: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version-file: package.json - package-manager-cache: false - - run: npm install --global "$(node -p "require('./package.json').packageManager")" - - run: pnpm install --frozen-lockfile - - run: python3 scripts/implement-durable-query-parity.py - - run: pnpm exec prettier --write src/persistence/ports.ts src/persistence/cloudflare/d1-runtime-persistence.ts src/application/durable-dispatcher.ts test/application/durable-query-parity.test.ts - - run: git rm .github/workflows/implement-durable-query-parity-once.yml scripts/implement-durable-query-parity.py - - run: pnpm vitest run test/application/durable-query-parity.test.ts test/application/durable-queries.test.ts - - run: pnpm check - - run: pnpm test:coverage - - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add src/persistence/ports.ts src/persistence/cloudflare/d1-runtime-persistence.ts src/application/durable-dispatcher.ts test/application/durable-query-parity.test.ts - git commit -m "feat: complete durable query parity" - git push diff --git a/scripts/implement-durable-query-parity.py b/scripts/implement-durable-query-parity.py deleted file mode 100644 index 63ff59b..0000000 --- a/scripts/implement-durable-query-parity.py +++ /dev/null @@ -1,52 +0,0 @@ -from pathlib import Path - - -def replace_once(path: str, old: str, new: str) -> None: - file = Path(path) - text = file.read_text() - if old not in text: - raise SystemExit(f'target not found in {path}: {old[:80]!r}') - file.write_text(text.replace(old, new, 1)) - - -replace_once( - 'src/persistence/ports.ts', - '''export interface PendingHumanPermission {\n request: PermissionRequest;\n latestDecision: PermissionDecision;\n}\n''', - '''export interface PendingHumanPermission {\n request: PermissionRequest;\n latestDecision: PermissionDecision;\n}\n\nexport interface TaskExecutionView {\n task: Task;\n lease?: Lease;\n latestCheckpoint?: Checkpoint;\n}\n''', -) - -replace_once( - 'src/persistence/ports.ts', - ''' getPermissionRequest(\n workspaceId: string,\n requestId: string,\n ): Promise;\n loadWorkspaceState(workspaceId: string): Promise;\n''', - ''' getPermissionRequest(\n workspaceId: string,\n requestId: string,\n ): Promise;\n listGoals(workspaceId: string, limit: number, offset?: number): Promise;\n listGoalTasks(\n workspaceId: string,\n goalId: string,\n limit?: number,\n offset?: number,\n ): Promise;\n getTaskExecutionView(\n workspaceId: string,\n taskId: string,\n now: string,\n sessionCutoff: string,\n ): Promise;\n loadWorkspaceState(workspaceId: string): Promise;\n''', -) - -replace_once( - 'src/persistence/cloudflare/d1-runtime-persistence.ts', - ''' type StoredCommandReceipt,\n type TaskOutcomeCommitInput,\n''', - ''' type StoredCommandReceipt,\n type TaskExecutionView,\n type TaskOutcomeCommitInput,\n''', -) - -replace_once( - 'src/persistence/cloudflare/d1-runtime-persistence.ts', - ''' async loadWorkspaceState(workspaceId: string): Promise {\n''', - ''' async listGoals(workspaceId: string, limit: number, offset = 0): Promise {\n return this.readRecords(\n `SELECT record_json FROM goals\n WHERE workspace_id = ? ORDER BY created_at_ms, id LIMIT ? OFFSET ?`,\n workspaceId,\n boundedLimit(limit),\n boundedOffset(offset),\n );\n }\n\n async getTaskExecutionView(\n workspaceId: string,\n taskId: string,\n now: string,\n sessionCutoff: string,\n ): Promise {\n const task = await this.getTask(workspaceId, taskId);\n if (!task) return undefined;\n const nowMs = timestampMs(now, 'Task execution view now');\n const cutoffMs = timestampMs(sessionCutoff, 'Task execution view session cutoff');\n const lease =\n task.status === 'running'\n ? await this.getEffectiveActiveLease(workspaceId, taskId, nowMs, cutoffMs)\n : undefined;\n const latestCheckpoint = await this.readRecord(\n `SELECT record_json FROM checkpoints\n WHERE workspace_id = ? AND task_id = ?\n ORDER BY created_at_ms DESC, id DESC LIMIT 1`,\n workspaceId,\n taskId,\n );\n return {\n task: clone(task),\n ...(lease === undefined ? {} : { lease: clone(lease) }),\n ...(latestCheckpoint === undefined\n ? {}\n : { latestCheckpoint: clone(latestCheckpoint) }),\n };\n }\n\n async loadWorkspaceState(workspaceId: string): Promise {\n''', -) - -replace_once( - 'src/persistence/cloudflare/d1-runtime-persistence.ts', - ''' private async listGoalTasks(workspaceId: string, goalId: string): Promise {\n return this.readRecords(\n `SELECT record_json FROM tasks\n WHERE workspace_id = ? AND goal_id = ? ORDER BY created_at_ms, id`,\n workspaceId,\n goalId,\n );\n }\n''', - ''' async listGoalTasks(\n workspaceId: string,\n goalId: string,\n limit?: number,\n offset = 0,\n ): Promise {\n if (limit === undefined) {\n return this.readRecords(\n `SELECT record_json FROM tasks\n WHERE workspace_id = ? AND goal_id = ? ORDER BY created_at_ms, id`,\n workspaceId,\n goalId,\n );\n }\n return this.readRecords(\n `SELECT record_json FROM tasks\n WHERE workspace_id = ? AND goal_id = ?\n ORDER BY created_at_ms, id LIMIT ? OFFSET ?`,\n workspaceId,\n goalId,\n boundedLimit(limit),\n boundedOffset(offset),\n );\n }\n''', -) - -replace_once( - 'src/application/durable-dispatcher.ts', - ''' case 'ListGoals':\n case 'ListGoalTasks':\n case 'GetTaskExecutionView':\n return unsupportedQuery(query);\n''', - ''' case 'ListGoals': {\n if (!(await options.persistence.getWorkspace(query.workspaceId))) {\n return queryFailure(query, 'NOT_FOUND', 'Durable Workspace was not found.');\n }\n const window = listWindow(query.limit, query.cursor);\n if (!window) return invalidListWindow(query);\n const rows = await options.persistence.listGoals(\n query.workspaceId,\n window.limit + 1,\n window.offset,\n );\n return querySuccess(query, pageRows(rows, window.limit, window.offset));\n }\n case 'ListGoalTasks': {\n if (!(await options.persistence.getGoal(query.workspaceId, query.goalId))) {\n return queryFailure(query, 'NOT_FOUND', 'Durable Goal was not found.');\n }\n const window = listWindow(query.limit, query.cursor);\n if (!window) return invalidListWindow(query);\n const rows = await options.persistence.listGoalTasks(\n query.workspaceId,\n query.goalId,\n window.limit + 1,\n window.offset,\n );\n return querySuccess(query, pageRows(rows, window.limit, window.offset));\n }\n case 'GetTaskExecutionView': {\n const now = options.now();\n return durableResourceQuery(\n query,\n await options.persistence.getTaskExecutionView(\n query.workspaceId,\n query.taskId,\n now.toISOString(),\n new Date(now.getTime() - options.sessionTimeoutMs).toISOString(),\n ),\n );\n }\n''', -) - -replace_once( - 'src/application/durable-dispatcher.ts', - '''function unsupportedQuery(query: ApplicationQuery): QueryFailure {\n return queryFailure(\n query,\n 'UNSUPPORTED_OPERATION',\n `${query.query} is not yet integrated in the durable application composition.`,\n );\n}\n\n''', - '', -) diff --git a/src/application/durable-dispatcher.ts b/src/application/durable-dispatcher.ts index 0223d56..55c92a3 100644 --- a/src/application/durable-dispatcher.ts +++ b/src/application/durable-dispatcher.ts @@ -232,10 +232,45 @@ async function dispatchDurableQuery( ); return querySuccess(query, pageRows(rows, window.limit, window.offset)); } - case 'ListGoals': - case 'ListGoalTasks': - case 'GetTaskExecutionView': - return unsupportedQuery(query); + case 'ListGoals': { + if (!(await options.persistence.getWorkspace(query.workspaceId))) { + return queryFailure(query, 'NOT_FOUND', 'Durable Workspace was not found.'); + } + const window = listWindow(query.limit, query.cursor); + if (!window) return invalidListWindow(query); + const rows = await options.persistence.listGoals( + query.workspaceId, + window.limit + 1, + window.offset, + ); + return querySuccess(query, pageRows(rows, window.limit, window.offset)); + } + case 'ListGoalTasks': { + if (!(await options.persistence.getGoal(query.workspaceId, query.goalId))) { + return queryFailure(query, 'NOT_FOUND', 'Durable Goal was not found.'); + } + const window = listWindow(query.limit, query.cursor); + if (!window) return invalidListWindow(query); + const rows = await options.persistence.listGoalTasks( + query.workspaceId, + query.goalId, + window.limit + 1, + window.offset, + ); + return querySuccess(query, pageRows(rows, window.limit, window.offset)); + } + case 'GetTaskExecutionView': { + const now = options.now(); + return durableResourceQuery( + query, + await options.persistence.getTaskExecutionView( + query.workspaceId, + query.taskId, + now.toISOString(), + new Date(now.getTime() - options.sessionTimeoutMs).toISOString(), + ), + ); + } } } catch (error) { if (error instanceof PersistenceError) return persistenceQueryFailure(query, error); @@ -758,14 +793,6 @@ function persistenceFailure(command: ApplicationCommand, error: PersistenceError } } -function unsupportedQuery(query: ApplicationQuery): QueryFailure { - return queryFailure( - query, - 'UNSUPPORTED_OPERATION', - `${query.query} is not yet integrated in the durable application composition.`, - ); -} - function querySuccess(query: ApplicationQuery, result: T): QueryResponse { return { protocolVersion: '0.1', diff --git a/src/persistence/cloudflare/d1-runtime-persistence.ts b/src/persistence/cloudflare/d1-runtime-persistence.ts index 69e9c68..7bbecf2 100644 --- a/src/persistence/cloudflare/d1-runtime-persistence.ts +++ b/src/persistence/cloudflare/d1-runtime-persistence.ts @@ -29,6 +29,7 @@ import { type PersistenceDomainTarget, type PersistenceDomainValidator, type StoredCommandReceipt, + type TaskExecutionView, type TaskOutcomeCommitInput, type TaskOutcomeCommitValue, type WorkspaceMutationCoordinator, @@ -1788,6 +1789,44 @@ export class D1RuntimePersistence implements DurableRuntimePersistence { ); } + async listGoals(workspaceId: string, limit: number, offset = 0): Promise { + return this.readRecords( + `SELECT record_json FROM goals + WHERE workspace_id = ? ORDER BY created_at_ms, id LIMIT ? OFFSET ?`, + workspaceId, + boundedLimit(limit), + boundedOffset(offset), + ); + } + + async getTaskExecutionView( + workspaceId: string, + taskId: string, + now: string, + sessionCutoff: string, + ): Promise { + const task = await this.getTask(workspaceId, taskId); + if (!task) return undefined; + const nowMs = timestampMs(now, 'Task execution view now'); + const cutoffMs = timestampMs(sessionCutoff, 'Task execution view session cutoff'); + const lease = + task.status === 'running' + ? await this.getEffectiveActiveLease(workspaceId, taskId, nowMs, cutoffMs) + : undefined; + const latestCheckpoint = await this.readRecord( + `SELECT record_json FROM checkpoints + WHERE workspace_id = ? AND task_id = ? + ORDER BY created_at_ms DESC, id DESC LIMIT 1`, + workspaceId, + taskId, + ); + return { + task: clone(task), + ...(lease === undefined ? {} : { lease: clone(lease) }), + ...(latestCheckpoint === undefined ? {} : { latestCheckpoint: clone(latestCheckpoint) }), + }; + } + async loadWorkspaceState(workspaceId: string): Promise { const workspaceRecord = await this.getWorkspace(workspaceId); if (!workspaceRecord) return undefined; @@ -2598,12 +2637,28 @@ export class D1RuntimePersistence implements DurableRuntimePersistence { return row ? Number(row.last_fencing_token) : undefined; } - private async listGoalTasks(workspaceId: string, goalId: string): Promise { + async listGoalTasks( + workspaceId: string, + goalId: string, + limit?: number, + offset = 0, + ): Promise { + if (limit === undefined) { + return this.readRecords( + `SELECT record_json FROM tasks + WHERE workspace_id = ? AND goal_id = ? ORDER BY created_at_ms, id`, + workspaceId, + goalId, + ); + } return this.readRecords( `SELECT record_json FROM tasks - WHERE workspace_id = ? AND goal_id = ? ORDER BY created_at_ms, id`, + WHERE workspace_id = ? AND goal_id = ? + ORDER BY created_at_ms, id LIMIT ? OFFSET ?`, workspaceId, goalId, + boundedLimit(limit), + boundedOffset(offset), ); } diff --git a/src/persistence/ports.ts b/src/persistence/ports.ts index 7754ff6..ae632cf 100644 --- a/src/persistence/ports.ts +++ b/src/persistence/ports.ts @@ -106,6 +106,12 @@ export interface PendingHumanPermission { latestDecision: PermissionDecision; } +export interface TaskExecutionView { + task: Task; + lease?: Lease; + latestCheckpoint?: Checkpoint; +} + export interface WorkspaceMutationCoordinator { runSerialized(workspaceId: string, operation: () => Promise): Promise; } @@ -306,6 +312,19 @@ export interface DurableRuntimePersistence { workspaceId: string, requestId: string, ): Promise; + listGoals(workspaceId: string, limit: number, offset?: number): Promise; + listGoalTasks( + workspaceId: string, + goalId: string, + limit?: number, + offset?: number, + ): Promise; + getTaskExecutionView( + workspaceId: string, + taskId: string, + now: string, + sessionCutoff: string, + ): Promise; loadWorkspaceState(workspaceId: string): Promise; listClaimableTasks( workspaceId: string, From ef5fdd4fb6d85b9ac2f75b97b0d641a8f5ca1299 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:11:47 +0500 Subject: [PATCH 11/16] chore: harden task execution view once --- .../harden-task-execution-view-once.yml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/harden-task-execution-view-once.yml diff --git a/.github/workflows/harden-task-execution-view-once.yml b/.github/workflows/harden-task-execution-view-once.yml new file mode 100644 index 0000000..5d3db90 --- /dev/null +++ b/.github/workflows/harden-task-execution-view-once.yml @@ -0,0 +1,50 @@ +name: Harden task execution view once + +on: + push: + branches: + - feature/durable-query-parity + paths: + - .github/workflows/harden-task-execution-view-once.yml + +permissions: + contents: write + +jobs: + harden: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + with: + node-version-file: package.json + package-manager-cache: false + - run: npm install --global "$(node -p "require('./package.json').packageManager")" + - run: pnpm install --frozen-lockfile + - run: | + python3 - <<'PY' + from pathlib import Path + path = Path('src/persistence/cloudflare/d1-runtime-persistence.ts') + text = path.read_text() + marker = '''interface PendingPermissionRow {\n request_json: string;\n decision_json: string;\n}\n''' + replacement = marker + '''\ninterface TaskExecutionViewRow {\n task_json: string;\n lease_json: string | null;\n checkpoint_json: string | null;\n}\n''' + if marker not in text: + raise SystemExit('row marker not found') + text = text.replace(marker, replacement, 1) + start = text.index(' async getTaskExecutionView(\n') + end = text.index('\n\n async loadWorkspaceState(', start) + method = ''' async getTaskExecutionView(\n workspaceId: string,\n taskId: string,\n now: string,\n sessionCutoff: string,\n ): Promise {\n const row = await this.first(\n `SELECT\n t.record_json AS task_json,\n CASE WHEN t.status = 'running' THEN (\n SELECT l.record_json\n FROM leases l\n JOIN sessions s\n ON s.workspace_id = l.workspace_id AND s.id = l.session_id\n WHERE l.workspace_id = t.workspace_id\n AND l.task_id = t.id\n AND l.status = 'active'\n AND l.expires_at_ms > ?\n AND s.status = 'active'\n AND s.last_seen_at_ms > ?\n ORDER BY l.fencing_token DESC\n LIMIT 1\n ) ELSE NULL END AS lease_json,\n (\n SELECT c.record_json\n FROM checkpoints c\n WHERE c.workspace_id = t.workspace_id AND c.task_id = t.id\n ORDER BY c.created_at_ms DESC, c.id DESC\n LIMIT 1\n ) AS checkpoint_json\n FROM tasks t\n WHERE t.workspace_id = ? AND t.id = ?`,\n timestampMs(now, 'Task execution view now'),\n timestampMs(sessionCutoff, 'Task execution view session cutoff'),\n workspaceId,\n taskId,\n );\n if (!row) return undefined;\n return {\n task: parseJson(row.task_json, 'Task execution view Task'),\n ...(row.lease_json === null\n ? {}\n : { lease: parseJson(row.lease_json, 'Task execution view Lease') }),\n ...(row.checkpoint_json === null\n ? {}\n : {\n latestCheckpoint: parseJson(\n row.checkpoint_json,\n 'Task execution view Checkpoint',\n ),\n }),\n };\n }''' + text = text[:start] + method + text[end:] + path.write_text(text) + PY + - run: pnpm exec prettier --write src/persistence/cloudflare/d1-runtime-persistence.ts + - run: git rm .github/workflows/harden-task-execution-view-once.yml + - run: pnpm vitest run test/application/durable-query-parity.test.ts test/application/durable-queries.test.ts + - run: pnpm check + - run: pnpm test:coverage + - run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add src/persistence/cloudflare/d1-runtime-persistence.ts + git commit -m "fix: read task execution view atomically" + git push From 424ab735bcd2df878e4eb5d194dd592966b273ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:12:32 +0000 Subject: [PATCH 12/16] fix: read task execution view atomically --- .../harden-task-execution-view-once.yml | 50 --------------- .../cloudflare/d1-runtime-persistence.ts | 62 ++++++++++++++----- 2 files changed, 47 insertions(+), 65 deletions(-) delete mode 100644 .github/workflows/harden-task-execution-view-once.yml diff --git a/.github/workflows/harden-task-execution-view-once.yml b/.github/workflows/harden-task-execution-view-once.yml deleted file mode 100644 index 5d3db90..0000000 --- a/.github/workflows/harden-task-execution-view-once.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Harden task execution view once - -on: - push: - branches: - - feature/durable-query-parity - paths: - - .github/workflows/harden-task-execution-view-once.yml - -permissions: - contents: write - -jobs: - harden: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version-file: package.json - package-manager-cache: false - - run: npm install --global "$(node -p "require('./package.json').packageManager")" - - run: pnpm install --frozen-lockfile - - run: | - python3 - <<'PY' - from pathlib import Path - path = Path('src/persistence/cloudflare/d1-runtime-persistence.ts') - text = path.read_text() - marker = '''interface PendingPermissionRow {\n request_json: string;\n decision_json: string;\n}\n''' - replacement = marker + '''\ninterface TaskExecutionViewRow {\n task_json: string;\n lease_json: string | null;\n checkpoint_json: string | null;\n}\n''' - if marker not in text: - raise SystemExit('row marker not found') - text = text.replace(marker, replacement, 1) - start = text.index(' async getTaskExecutionView(\n') - end = text.index('\n\n async loadWorkspaceState(', start) - method = ''' async getTaskExecutionView(\n workspaceId: string,\n taskId: string,\n now: string,\n sessionCutoff: string,\n ): Promise {\n const row = await this.first(\n `SELECT\n t.record_json AS task_json,\n CASE WHEN t.status = 'running' THEN (\n SELECT l.record_json\n FROM leases l\n JOIN sessions s\n ON s.workspace_id = l.workspace_id AND s.id = l.session_id\n WHERE l.workspace_id = t.workspace_id\n AND l.task_id = t.id\n AND l.status = 'active'\n AND l.expires_at_ms > ?\n AND s.status = 'active'\n AND s.last_seen_at_ms > ?\n ORDER BY l.fencing_token DESC\n LIMIT 1\n ) ELSE NULL END AS lease_json,\n (\n SELECT c.record_json\n FROM checkpoints c\n WHERE c.workspace_id = t.workspace_id AND c.task_id = t.id\n ORDER BY c.created_at_ms DESC, c.id DESC\n LIMIT 1\n ) AS checkpoint_json\n FROM tasks t\n WHERE t.workspace_id = ? AND t.id = ?`,\n timestampMs(now, 'Task execution view now'),\n timestampMs(sessionCutoff, 'Task execution view session cutoff'),\n workspaceId,\n taskId,\n );\n if (!row) return undefined;\n return {\n task: parseJson(row.task_json, 'Task execution view Task'),\n ...(row.lease_json === null\n ? {}\n : { lease: parseJson(row.lease_json, 'Task execution view Lease') }),\n ...(row.checkpoint_json === null\n ? {}\n : {\n latestCheckpoint: parseJson(\n row.checkpoint_json,\n 'Task execution view Checkpoint',\n ),\n }),\n };\n }''' - text = text[:start] + method + text[end:] - path.write_text(text) - PY - - run: pnpm exec prettier --write src/persistence/cloudflare/d1-runtime-persistence.ts - - run: git rm .github/workflows/harden-task-execution-view-once.yml - - run: pnpm vitest run test/application/durable-query-parity.test.ts test/application/durable-queries.test.ts - - run: pnpm check - - run: pnpm test:coverage - - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add src/persistence/cloudflare/d1-runtime-persistence.ts - git commit -m "fix: read task execution view atomically" - git push diff --git a/src/persistence/cloudflare/d1-runtime-persistence.ts b/src/persistence/cloudflare/d1-runtime-persistence.ts index 7bbecf2..ac237e5 100644 --- a/src/persistence/cloudflare/d1-runtime-persistence.ts +++ b/src/persistence/cloudflare/d1-runtime-persistence.ts @@ -76,6 +76,12 @@ interface PendingPermissionRow { decision_json: string; } +interface TaskExecutionViewRow { + task_json: string; + lease_json: string | null; + checkpoint_json: string | null; +} + export class D1RuntimePersistence implements DurableRuntimePersistence { private readonly database: D1DatabaseLike; private readonly coordinator: WorkspaceMutationCoordinator; @@ -1805,25 +1811,51 @@ export class D1RuntimePersistence implements DurableRuntimePersistence { now: string, sessionCutoff: string, ): Promise { - const task = await this.getTask(workspaceId, taskId); - if (!task) return undefined; - const nowMs = timestampMs(now, 'Task execution view now'); - const cutoffMs = timestampMs(sessionCutoff, 'Task execution view session cutoff'); - const lease = - task.status === 'running' - ? await this.getEffectiveActiveLease(workspaceId, taskId, nowMs, cutoffMs) - : undefined; - const latestCheckpoint = await this.readRecord( - `SELECT record_json FROM checkpoints - WHERE workspace_id = ? AND task_id = ? - ORDER BY created_at_ms DESC, id DESC LIMIT 1`, + const row = await this.first( + `SELECT + t.record_json AS task_json, + CASE WHEN t.status = 'running' THEN ( + SELECT l.record_json + FROM leases l + JOIN sessions s + ON s.workspace_id = l.workspace_id AND s.id = l.session_id + WHERE l.workspace_id = t.workspace_id + AND l.task_id = t.id + AND l.status = 'active' + AND l.expires_at_ms > ? + AND s.status = 'active' + AND s.last_seen_at_ms > ? + ORDER BY l.fencing_token DESC + LIMIT 1 + ) ELSE NULL END AS lease_json, + ( + SELECT c.record_json + FROM checkpoints c + WHERE c.workspace_id = t.workspace_id AND c.task_id = t.id + ORDER BY c.created_at_ms DESC, c.id DESC + LIMIT 1 + ) AS checkpoint_json + FROM tasks t + WHERE t.workspace_id = ? AND t.id = ?`, + timestampMs(now, 'Task execution view now'), + timestampMs(sessionCutoff, 'Task execution view session cutoff'), workspaceId, taskId, ); + if (!row) return undefined; return { - task: clone(task), - ...(lease === undefined ? {} : { lease: clone(lease) }), - ...(latestCheckpoint === undefined ? {} : { latestCheckpoint: clone(latestCheckpoint) }), + task: parseJson(row.task_json, 'Task execution view Task'), + ...(row.lease_json === null + ? {} + : { lease: parseJson(row.lease_json, 'Task execution view Lease') }), + ...(row.checkpoint_json === null + ? {} + : { + latestCheckpoint: parseJson( + row.checkpoint_json, + 'Task execution view Checkpoint', + ), + }), }; } From d35452ec77a144dd5a493ac120edbbcec19272ea Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:14:34 +0500 Subject: [PATCH 13/16] chore: stage durable query parity docs reconciliation --- .../reconcile-durable-query-parity-docs.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 scripts/reconcile-durable-query-parity-docs.py diff --git a/scripts/reconcile-durable-query-parity-docs.py b/scripts/reconcile-durable-query-parity-docs.py new file mode 100644 index 0000000..e6f4802 --- /dev/null +++ b/scripts/reconcile-durable-query-parity-docs.py @@ -0,0 +1,83 @@ +from pathlib import Path + + +def replace_once(path: str, old: str, new: str) -> None: + file = Path(path) + text = file.read_text() + if old not in text: + raise SystemExit(f'target not found in {path}: {old[:120]!r}') + file.write_text(text.replace(old, new, 1)) + + +replace_once( + 'docs/CURRENT_STATE.md', + '**Last reconciled:** 2026-08-29', + '**Last reconciled:** 2026-08-30', +) +replace_once( + 'docs/CURRENT_STATE.md', + '- Explicit durable read ports and application queries are implemented for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`. List queries use bounded deterministic cursor paging. `ListClaimableTasks` includes capability-compatible `ready` Tasks plus `running` Tasks whose prior Lease/Session authority is no longer effective at the authoritative server time; work acquisition still revalidates authority atomically at `ClaimTask`.', + '- Explicit durable read ports and application queries cover the complete ADR-0005 v0.1 query surface: `GetWorkspace`, `ListGoals`, `GetGoal`, `ListGoalTasks`, `GetTask`, `GetTaskExecutionView`, `ListClaimableTasks`, `GetLease`, `GetAgent`, `GetSession`, `ListTaskCheckpoints`, `GetPermissionRequest`, `ListPendingHumanPermissions`, and `ListPermissionDecisions`. List queries use bounded deterministic cursor paging. `ListClaimableTasks` includes capability-compatible `ready` Tasks plus `running` Tasks whose prior Lease/Session authority is no longer effective at authoritative server time; work acquisition still revalidates authority atomically at `ClaimTask`. `GetTaskExecutionView` reads Task, effective Lease, and latest Checkpoint through one D1 statement so the projection is coherent at one database read snapshot.', +) +replace_once( + 'docs/CURRENT_STATE.md', + '- Durable queries `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain explicitly unsupported rather than inferred from retained application state.\n', + '- The locally verified durable application composition now has command and query parity with the complete ADR-0005 v0.1 protocol surface; remaining product gaps are external integration and deployed-reference verification rather than retained in-memory fallbacks.\n', +) +replace_once( + 'docs/CURRENT_STATE.md', + '- Add the remaining bounded durable queries only where required by agent/human workflows.\n', + '', +) +replace_once( + 'docs/CURRENT_STATE.md', + '- Durable task-outcome RED Quality run `33278105807` kept the previous **130 tests green** while both new outcome regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. GREEN run `33278271723` passed full `pnpm check` and coverage after self-cleaning the one-time implementation harness. Review-hardening run `33278367867` then proved persisted FailTask Lease state, lost-response BlockTask replay, durable blocked Checkpoint storage, and complete persisted ResumeTask state. Atomicity run `33278493339` added D1-like mid-batch fault injection and passed **32/32 test files and 133/133 tests** plus coverage at 84.99% statements, 74.15% branches, 96.36% functions, and 86.73% lines; the injected failure rolls back Checkpoint, Task, Lease, and receipt together before an exact retry succeeds. Durable retry/cancellation review hardening culminated in run `33283962742`, which passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines. The regressions cover lost-response replay, `CancelTask` without an effective Lease, `CancelGoal` mid-batch rollback, stale Goal task admission, false-success receipt prevention after a lost CAS, and recovery-`ClaimTask` races against both Task and Goal cancellation.', + '- Durable task-outcome RED Quality run `33278105807` kept the previous **130 tests green** while both new outcome regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. GREEN run `33278271723` passed full `pnpm check` and coverage after self-cleaning the one-time implementation harness. Review-hardening run `33278367867` then proved persisted FailTask Lease state, lost-response BlockTask replay, durable blocked Checkpoint storage, and complete persisted ResumeTask state. Atomicity run `33278493339` added D1-like mid-batch fault injection and passed **32/32 test files and 133/133 tests** plus coverage at 84.99% statements, 74.15% branches, 96.36% functions, and 86.73% lines; the injected failure rolls back Checkpoint, Task, Lease, and receipt together before an exact retry succeeds. Durable retry/cancellation review hardening culminated in run `33283962742`, which passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines. The regressions cover lost-response replay, `CancelTask` without an effective Lease, `CancelGoal` mid-batch rollback, stale Goal task admission, false-success receipt prevention after a lost CAS, and recovery-`ClaimTask` races against both Task and Goal cancellation. Durable query-parity RED Quality run `33284893955` kept those **143 tests green** while exactly three new query regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. Query hardening run `33285112259` then passed **34/34 test files and 146/146 tests**, full `pnpm check`, and coverage at 85.1% statements, 74.51% branches, 96.79% functions, and 86.86% lines after `ListGoals`, `ListGoalTasks`, and the single-statement `GetTaskExecutionView` projection were made durable.', +) + +replace_once( + 'docs/roadmap/V0_1.md', + '**Status:** v0.1 command surface implemented and verified; durable command/query composition remains intentionally partial.', + '**Status:** v0.1 command/query surface implemented and verified; durable protocol parity is complete locally.', +) +replace_once( + 'docs/roadmap/V0_1.md', + 'Durable read/query support now includes single-resource execution/permission reads, checkpoint and permission-decision history, the pending-human queue, and advisory claimable work. `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain pending.', + 'Durable read/query support now covers the complete ADR-0005 v0.1 query surface, including bounded `ListGoals`, bounded `ListGoalTasks`, and `GetTaskExecutionView`. The execution view returns Task, effective Lease, and latest Checkpoint from one durable database statement; advisory `ListClaimableTasks` still revalidates execution authority atomically at `ClaimTask`.', +) +replace_once( + 'docs/roadmap/V0_1.md', + '- durable queries for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`;', + '- durable queries for the complete ADR-0005 v0.1 surface, including bounded `ListGoals` / `ListGoalTasks`, coherent `GetTaskExecutionView`, execution-resource reads, checkpoint and permission history, pending-human work, and advisory `ListClaimableTasks`;', +) +replace_once( + 'docs/roadmap/V0_1.md', + 'Durable Session/Lease liveness is now covered through HTTP restart E2E: heartbeat does not extend Lease authority, renew preserves the fence, release permits higher-fence recovery, and EndSession durably revokes active Lease authority before recovery. Durable lifecycle/cancellation command parity is complete locally; remaining local work is bounded query parity plus persistence-concurrency hardening and final end-to-end reconciliation. The executed SQLite/D1-like tests do not substitute for deployed Cloudflare verification.', + 'Durable Session/Lease liveness is now covered through HTTP restart E2E: heartbeat does not extend Lease authority, renew preserves the fence, release permits higher-fence recovery, and EndSession durably revokes active Lease authority before recovery. Durable command and query parity is complete locally; remaining local hardening is correctness-focused persistence/concurrency review plus real agent/deployed-reference integration. The executed SQLite/D1-like tests do not substitute for deployed Cloudflare verification.', +) +replace_once( + 'docs/roadmap/V0_1.md', + '**Current gap to that condition:** at least one real agent-host integration, the three remaining durable read queries, persistence/deployed-reference-runtime hardening, and real Cloudflare verification are outstanding. The local durable HTTP composition now proves the command-side persistence/restart contract, but it is not yet a real-agent or deployed-Cloudflare product path.', + '**Current gap to that condition:** at least one real agent-host integration, persistence/deployed-reference-runtime hardening, and real Cloudflare verification are outstanding. The local durable HTTP composition now proves full v0.1 command/query persistence and restart semantics, but it is not yet a real-agent or deployed-Cloudflare product path.', +) + +replace_once( + 'CHANGELOG.md', + '- Explicit durable query ports and bounded query support for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`.', + '- Explicit durable query ports and bounded query support for the complete ADR-0005 v0.1 surface: `GetWorkspace`, `ListGoals`, `GetGoal`, `ListGoalTasks`, `GetTask`, `GetTaskExecutionView`, `ListClaimableTasks`, `GetLease`, `GetAgent`, `GetSession`, `ListTaskCheckpoints`, `GetPermissionRequest`, `ListPendingHumanPermissions`, and `ListPermissionDecisions`.', +) +replace_once( + 'CHANGELOG.md', + '- D1 mutation batches now use transaction-aborting guard rows for conditional mutation races. Task creation/retry revalidate an active parent Goal inside the database batch; Goal cancellation terminalizes the Goal only after its cancellable Tasks/Leases and only when no cancellable work remains; claim/cancellation share fencing-counter and Task-state predicates so neither can return success with execution authority beneath cancelled state.', + '- D1 mutation batches now use transaction-aborting guard rows for conditional mutation races. Task creation/retry revalidate an active parent Goal inside the database batch; Goal cancellation terminalizes the Goal only after its cancellable Tasks/Leases and only when no cancellable work remains; claim/cancellation share fencing-counter and Task-state predicates so neither can return success with execution authority beneath cancelled state. `GetTaskExecutionView` now derives Task, effective Lease, and latest Checkpoint in one D1 statement so the read projection cannot mix separate database snapshots.', +) +replace_once( + 'CHANGELOG.md', + '- Durable retry/cancellation hardening run `33283962742` passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines after transaction-CAS, stale Goal admission, no-Lease cancellation, mid-batch rollback, false-receipt, and cancellation-versus-recovery claim regressions were made green.', + '- Durable retry/cancellation hardening run `33283962742` passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines after transaction-CAS, stale Goal admission, no-Lease cancellation, mid-batch rollback, false-receipt, and cancellation-versus-recovery claim regressions were made green. Durable query-parity RED Quality run `33284893955` preserved those **143 passing tests** while exactly three new query regressions failed on `UNSUPPORTED_OPERATION`; hardening run `33285112259` then passed **34/34 test files and 146/146 tests**, full `pnpm check`, and coverage at 85.1% statements, 74.51% branches, 96.79% functions, and 86.86% lines.', +) +replace_once( + 'CHANGELOG.md', + '- The durable dispatcher supports the complete ADR-0005 v0.1 command surface. The remaining explicit durable application gaps are read-side: `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView`.\n- Durable application queries `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain explicitly unsupported.\n', + '- The durable dispatcher supports the complete ADR-0005 v0.1 command and query surface against the local SQLite D1-like reference harness; this does not imply deployed Cloudflare verification.\n', +) From a8fd2ce0913142941407f3299ae548a7761a0f25 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:14:45 +0500 Subject: [PATCH 14/16] chore: reconcile durable query parity docs once --- ...concile-durable-query-parity-docs-once.yml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/reconcile-durable-query-parity-docs-once.yml diff --git a/.github/workflows/reconcile-durable-query-parity-docs-once.yml b/.github/workflows/reconcile-durable-query-parity-docs-once.yml new file mode 100644 index 0000000..210bad2 --- /dev/null +++ b/.github/workflows/reconcile-durable-query-parity-docs-once.yml @@ -0,0 +1,34 @@ +name: Reconcile durable query parity docs once + +on: + push: + branches: + - feature/durable-query-parity + paths: + - .github/workflows/reconcile-durable-query-parity-docs-once.yml + +permissions: + contents: write + +jobs: + reconcile: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + with: + node-version-file: package.json + package-manager-cache: false + - run: npm install --global "$(node -p "require('./package.json').packageManager")" + - run: pnpm install --frozen-lockfile + - run: python3 scripts/reconcile-durable-query-parity-docs.py + - run: pnpm exec prettier --write docs/CURRENT_STATE.md docs/roadmap/V0_1.md CHANGELOG.md + - run: git rm .github/workflows/reconcile-durable-query-parity-docs-once.yml scripts/reconcile-durable-query-parity-docs.py + - run: pnpm check + - run: pnpm test:coverage + - run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add docs/CURRENT_STATE.md docs/roadmap/V0_1.md CHANGELOG.md + git commit -m "docs: reconcile durable query parity" + git push From 5ed1f60f1fe3bf82904133589f8508a32a3e59a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:15:32 +0000 Subject: [PATCH 15/16] docs: reconcile durable query parity --- ...concile-durable-query-parity-docs-once.yml | 34 -------- CHANGELOG.md | 9 +- docs/CURRENT_STATE.md | 9 +- docs/roadmap/V0_1.md | 10 +-- .../reconcile-durable-query-parity-docs.py | 83 ------------------- 5 files changed, 13 insertions(+), 132 deletions(-) delete mode 100644 .github/workflows/reconcile-durable-query-parity-docs-once.yml delete mode 100644 scripts/reconcile-durable-query-parity-docs.py diff --git a/.github/workflows/reconcile-durable-query-parity-docs-once.yml b/.github/workflows/reconcile-durable-query-parity-docs-once.yml deleted file mode 100644 index 210bad2..0000000 --- a/.github/workflows/reconcile-durable-query-parity-docs-once.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Reconcile durable query parity docs once - -on: - push: - branches: - - feature/durable-query-parity - paths: - - .github/workflows/reconcile-durable-query-parity-docs-once.yml - -permissions: - contents: write - -jobs: - reconcile: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e - with: - node-version-file: package.json - package-manager-cache: false - - run: npm install --global "$(node -p "require('./package.json').packageManager")" - - run: pnpm install --frozen-lockfile - - run: python3 scripts/reconcile-durable-query-parity-docs.py - - run: pnpm exec prettier --write docs/CURRENT_STATE.md docs/roadmap/V0_1.md CHANGELOG.md - - run: git rm .github/workflows/reconcile-durable-query-parity-docs-once.yml scripts/reconcile-durable-query-parity-docs.py - - run: pnpm check - - run: pnpm test:coverage - - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add docs/CURRENT_STATE.md docs/roadmap/V0_1.md CHANGELOG.md - git commit -m "docs: reconcile durable query parity" - git push diff --git a/CHANGELOG.md b/CHANGELOG.md index dad5f6e..ffba3db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ The project is in `0.x` development and does not yet have a public product relea - Durable application dispatcher that reloads authoritative persisted state for each supported command, reuses the canonical runtime semantics, and commits through explicit persistence methods without a retained in-memory fallback. - Deferred `ClaimTask` receipt construction so the persisted/replayed response contains the fencing token allocated by the persistence transaction rather than a speculative runtime token. - First durable command loop for `RegisterAgent`, `StartSession`, `CreateGoal`, `CreateTask`, `ClaimTask`, `RecordCheckpoint`, `RequestPermission`, `RecordPermissionDecision`, and `CompleteTask`. -- Explicit durable query ports and bounded query support for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`. +- Explicit durable query ports and bounded query support for the complete ADR-0005 v0.1 surface: `GetWorkspace`, `ListGoals`, `GetGoal`, `ListGoalTasks`, `GetTask`, `GetTaskExecutionView`, `ListClaimableTasks`, `GetLease`, `GetAgent`, `GetSession`, `ListTaskCheckpoints`, `GetPermissionRequest`, `ListPendingHumanPermissions`, and `ListPermissionDecisions`. - Bounded HTTP and MCP adapters over one application/protocol surface, preserving protocol error/idempotency envelopes and avoiding generic shell/filesystem/browser/action authority tools. - In-memory application dispatcher for all ADR-0005 commands plus bounded queries. - HTTP/application end-to-end regression covering `RegisterAgent -> StartSession -> CreateGoal -> CreateTask -> ClaimTask -> RecordCheckpoint -> RequestPermission(repository.write) -> RecordPermissionDecision(ALLOW) -> CompleteTask`, followed by HTTP verification of final Task, Goal, Lease, and checkpoint state. @@ -59,7 +59,7 @@ The project is in `0.x` development and does not yet have a public product relea - Admitted terminal semantic failures in the supported durable command loop now persist immutable `outcomeKind: error` command receipts and replay after restart instead of re-executing the command. - Durable Session/Lease liveness composition now supports `HeartbeatSession`, `EndSession`, `RenewLease`, and `ReleaseLease` with atomic mutation receipts, restart-safe replay, stable renewal fencing, and recoverable released/revoked execution authority. - Durable task-outcome persistence now revalidates execution/revision authority at commit time and stores failure/block transitions and their released Lease plus Checkpoint in one batch; `ResumeTask` recomputes dependency readiness and removes the blocking reason without granting a Lease. -- D1 mutation batches now use transaction-aborting guard rows for conditional mutation races. Task creation/retry revalidate an active parent Goal inside the database batch; Goal cancellation terminalizes the Goal only after its cancellable Tasks/Leases and only when no cancellable work remains; claim/cancellation share fencing-counter and Task-state predicates so neither can return success with execution authority beneath cancelled state. +- D1 mutation batches now use transaction-aborting guard rows for conditional mutation races. Task creation/retry revalidate an active parent Goal inside the database batch; Goal cancellation terminalizes the Goal only after its cancellable Tasks/Leases and only when no cancellable work remains; claim/cancellation share fencing-counter and Task-state predicates so neither can return success with execution authority beneath cancelled state. `GetTaskExecutionView` now derives Task, effective Lease, and latest Checkpoint in one D1 statement so the read projection cannot mix separate database snapshots. ### Verification @@ -78,7 +78,7 @@ The project is in `0.x` development and does not yet have a public product relea - Permanent Quality run `33274903333` passed the restart-safe durable HTTP E2E tree with **29/29 test files and 123/123 tests**, plus `pnpm test:coverage`. Overall coverage reported 85.3% statements, 73.3% branches, 96.15% functions, and 86.95% lines. - Final correctness-review RED run `33275182068` failed exactly the two new terminal-error-receipt and recovery-discovery regressions while the previous 123 tests passed. Review-fix run `33275312677` then passed focused regressions, full `pnpm check`, and coverage with **30/30 test files and 125/125 tests**; overall coverage was 85.43% statements, 73.5% branches, 96.18% functions, and 87.07% lines. - Durable task-outcome RED run `33278105807` preserved the previous 130 passing tests while the new FailTask/BlockTask regressions failed at the expected unsupported boundary. Review/atomicity run `33278493339` passed **32/32 test files and 133/133 tests**, plus coverage at 84.99% statements, 74.15% branches, 96.36% functions, and 86.73% lines, including a forced mid-batch rollback followed by successful exact retry. -- Durable retry/cancellation hardening run `33283962742` passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines after transaction-CAS, stale Goal admission, no-Lease cancellation, mid-batch rollback, false-receipt, and cancellation-versus-recovery claim regressions were made green. +- Durable retry/cancellation hardening run `33283962742` passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines after transaction-CAS, stale Goal admission, no-Lease cancellation, mid-batch rollback, false-receipt, and cancellation-versus-recovery claim regressions were made green. Durable query-parity RED Quality run `33284893955` preserved those **143 passing tests** while exactly three new query regressions failed on `UNSUPPORTED_OPERATION`; hardening run `33285112259` then passed **34/34 test files and 146/146 tests**, full `pnpm check`, and coverage at 85.1% statements, 74.51% branches, 96.79% functions, and 86.86% lines. - Frozen installation resolves `@mindrail/contracts 0.0.0 <- packages/contracts`, confirming the root runtime uses the workspace contract package. - No new third-party runtime dependency was introduced for schema admission, protocol admission, bootstrap, transport, permission, persistence composition, rehydration, or durable query semantics. @@ -86,8 +86,7 @@ The project is in `0.x` development and does not yet have a public product relea - `Quality` is not yet enforced as a required `main` merge gate; repository protection remains tracked separately in issue #3. - The durable application composition is verified against the local SQLite D1-like test harness, not a deployed Cloudflare Worker/Durable Object/D1 environment. -- The durable dispatcher supports the complete ADR-0005 v0.1 command surface. The remaining explicit durable application gaps are read-side: `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView`. -- Durable application queries `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain explicitly unsupported. +- The durable dispatcher supports the complete ADR-0005 v0.1 command and query surface against the local SQLite D1-like reference harness; this does not imply deployed Cloudflare verification. - MindRail does not yet expose a verified deployed Cloudflare control-plane service, GitHub adapter, real Codex/ChatGPT integration, or unattended continuation of a real external agent across host/platform termination. - The deterministic v0.1 permission policy is intentionally small and is not an IAM system, credential manager, arbitrary policy DSL, or model-based authority mechanism. - MindRail-specific BUSL parameters and external-contribution licensing mechanics still require professional legal review before material reliance. diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md index dacc064..55e6491 100644 --- a/docs/CURRENT_STATE.md +++ b/docs/CURRENT_STATE.md @@ -1,6 +1,6 @@ # Current State -**Last reconciled:** 2026-08-29 +**Last reconciled:** 2026-08-30 This document describes what exists, not what is intended. Roadmap items are never evidence of implementation. @@ -32,7 +32,7 @@ The following facts are supported by repository state and executed GitHub Action - Durable task outcomes now preserve the same runtime semantics through persistence: `FailTask` and `BlockTask` atomically commit the Task transition, released Lease, terminal/blocked Checkpoint, and immutable command receipt; `ResumeTask` atomically commits the controller-authorized blocked-to-ready/pending Task transition and receipt without minting execution authority. `RetryTask`, `CancelTask`, and `CancelGoal` also commit through explicit durable mutations with controller authority, immutable receipts, Goal/Task/Lease revision checks, database-enforced Goal-versus-Task admission ordering, and fencing-counter guards that prevent recovery claims from surviving cancellation races. - Durable command replay first reads the persisted `(workspaceId, commandId)` receipt. Exact retries survive application/database-handle replacement, return `replayed: true`, preserve the immutable stored result/error snapshot, and reflect the current correlation id. Semantic command-id drift fails with `IDEMPOTENCY_CONFLICT`. - Admitted terminal semantic failures on the supported durable command loop are also persisted as immutable error receipts, so an exact retry after restart replays the original terminal error instead of silently re-executing the command. -- Explicit durable read ports and application queries are implemented for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`. List queries use bounded deterministic cursor paging. `ListClaimableTasks` includes capability-compatible `ready` Tasks plus `running` Tasks whose prior Lease/Session authority is no longer effective at the authoritative server time; work acquisition still revalidates authority atomically at `ClaimTask`. +- Explicit durable read ports and application queries cover the complete ADR-0005 v0.1 query surface: `GetWorkspace`, `ListGoals`, `GetGoal`, `ListGoalTasks`, `GetTask`, `GetTaskExecutionView`, `ListClaimableTasks`, `GetLease`, `GetAgent`, `GetSession`, `ListTaskCheckpoints`, `GetPermissionRequest`, `ListPendingHumanPermissions`, and `ListPermissionDecisions`. List queries use bounded deterministic cursor paging. `ListClaimableTasks` includes capability-compatible `ready` Tasks plus `running` Tasks whose prior Lease/Session authority is no longer effective at authoritative server time; work acquisition still revalidates authority atomically at `ClaimTask`. `GetTaskExecutionView` reads Task, effective Lease, and latest Checkpoint through one D1 statement so the projection is coherent at one database read snapshot. - HTTP and MCP adapters exist over the common application/protocol boundary. They preserve canonical error codes/idempotency envelopes, fail closed on authorization errors, enforce bounded structural admission, and do not expose generic shell/filesystem/browser/action authority tools. - The in-memory application dispatcher delegates all ADR-0005 v0.1 commands to the canonical runtime. A deterministic HTTP/application E2E covers the complete local bootstrap/execution/permission/completion path. - Durable HTTP E2E coverage runs the real HTTP adapter over the durable dispatcher and SQLite D1-like persistence file. It verifies: continuation of the same Lease/fence after closing and reopening the application/database handle; persistence of `HUMAN_REQUIRED` plus human decision history across restart; response-loss replay through a fresh dispatcher; and competing independent application instances preserving one effective Lease and advancing fencing from 1 to 2 after expiry/recovery. @@ -40,14 +40,14 @@ The following facts are supported by repository state and executed GitHub Action - Permanent `Quality` run `33274903333` on the durable HTTP E2E tree passed formatting, lint, strict TypeScript, generated-contract drift checks, **29/29 test files and 123/123 tests**, and coverage. Reported overall coverage was 85.3% statements, 73.3% branches, 96.15% functions, and 86.95% lines. - Final correctness review RED run `33275182068` demonstrated both remaining defects: the two new regressions failed because terminal semantic errors had no durable receipt and recovery work discovery omitted a `running` Task after its effective Lease expired, while the previous **123 tests passed**. Review-fix run `33275312677` then passed focused regressions, full `pnpm check`, and coverage with **30/30 test files and 125/125 tests**. Overall coverage was 85.43% statements, 73.5% branches, 96.18% functions, and 87.07% lines. - Durable Session/Lease liveness RED Quality run `33276800987` passed formatting/lint/typecheck/contracts and the previous **125 tests**, while all four new HTTP E2E regressions failed exactly because `HeartbeatSession`, `EndSession`, `RenewLease`, and `ReleaseLease` returned `UNSUPPORTED_OPERATION`. GREEN run `33276973691` then passed the focused 4/4 liveness E2E tests, full `pnpm check` with **31/31 test files and 129/129 tests**, and coverage at 85.19% statements, 73.88% branches, 96.3% functions, and 86.83% lines. -- Durable task-outcome RED Quality run `33278105807` kept the previous **130 tests green** while both new outcome regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. GREEN run `33278271723` passed full `pnpm check` and coverage after self-cleaning the one-time implementation harness. Review-hardening run `33278367867` then proved persisted FailTask Lease state, lost-response BlockTask replay, durable blocked Checkpoint storage, and complete persisted ResumeTask state. Atomicity run `33278493339` added D1-like mid-batch fault injection and passed **32/32 test files and 133/133 tests** plus coverage at 84.99% statements, 74.15% branches, 96.36% functions, and 86.73% lines; the injected failure rolls back Checkpoint, Task, Lease, and receipt together before an exact retry succeeds. Durable retry/cancellation review hardening culminated in run `33283962742`, which passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines. The regressions cover lost-response replay, `CancelTask` without an effective Lease, `CancelGoal` mid-batch rollback, stale Goal task admission, false-success receipt prevention after a lost CAS, and recovery-`ClaimTask` races against both Task and Goal cancellation. +- Durable task-outcome RED Quality run `33278105807` kept the previous **130 tests green** while both new outcome regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. GREEN run `33278271723` passed full `pnpm check` and coverage after self-cleaning the one-time implementation harness. Review-hardening run `33278367867` then proved persisted FailTask Lease state, lost-response BlockTask replay, durable blocked Checkpoint storage, and complete persisted ResumeTask state. Atomicity run `33278493339` added D1-like mid-batch fault injection and passed **32/32 test files and 133/133 tests** plus coverage at 84.99% statements, 74.15% branches, 96.36% functions, and 86.73% lines; the injected failure rolls back Checkpoint, Task, Lease, and receipt together before an exact retry succeeds. Durable retry/cancellation review hardening culminated in run `33283962742`, which passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines. The regressions cover lost-response replay, `CancelTask` without an effective Lease, `CancelGoal` mid-batch rollback, stale Goal task admission, false-success receipt prevention after a lost CAS, and recovery-`ClaimTask` races against both Task and Goal cancellation. Durable query-parity RED Quality run `33284893955` kept those **143 tests green** while exactly three new query regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. Query hardening run `33285112259` then passed **34/34 test files and 146/146 tests**, full `pnpm check`, and coverage at 85.1% statements, 74.51% branches, 96.79% functions, and 86.86% lines after `ListGoals`, `ListGoalTasks`, and the single-statement `GetTaskExecutionView` projection were made durable. - Runtime Surface PR #20 merged with post-merge Quality PASS. Persistence PR #24 merged with post-merge Quality #248 PASS. HTTP/MCP Transport PR #25 merged at `e142c1399aed5de3d8df53ad876499583728a6b4`; permanent Quality #249 and post-merge Quality #250 both passed full quality and coverage gates. - Permanent `Quality` CI remains least-privilege and uses pinned GitHub-owned action commits. ## Implemented but not yet fully deployed / externally integrated - The durable application composition is verified locally against the SQLite D1-like harness used by persistence tests. This is executable restart/concurrency evidence for the application/persistence contract, but it is **not** evidence of a deployed Cloudflare Worker, Durable Object, or production D1 environment. -- Durable queries `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain explicitly unsupported rather than inferred from retained application state. +- The locally verified durable application composition now has command and query parity with the complete ADR-0005 v0.1 protocol surface; remaining product gaps are external integration and deployed-reference verification rather than retained in-memory fallbacks. - No deployed Cloudflare Worker/Durable Object service is claimed. Deployment configuration, environment provisioning, deployed-runtime restart verification, and real Cloudflare concurrency verification remain outstanding. - The v0.1 permission policy is intentionally small, explicit, hard-coded, and versioned. It is not a policy DSL, IAM system, credential manager, model judge, or arbitrary-code policy runtime. - `Quality` is executable on pull requests and `main`, but issue #3 still tracks repository-level enforcement as a required merge gate. @@ -55,7 +55,6 @@ The following facts are supported by repository state and executed GitHub Action ## Next implementation slices - Audit and harden any remaining conditional durable mutations against the same database-CAS/receipt and Goal-level concurrency invariants before treating the local persistence composition as complete. -- Add the remaining bounded durable queries only where required by agent/human workflows. - Add GitHub integration while keeping GitHub as an adapter/projection rather than canonical state authority. - Add minimal real Codex, ChatGPT-compatible, generic MCP, and generic HTTP agent bootstrap/worker paths on top of the stable protocol/application boundary. - Deploy and verify the Cloudflare reference composition, including real Durable Object coordination and D1 restart/concurrency behavior, before making production-runtime claims. diff --git a/docs/roadmap/V0_1.md b/docs/roadmap/V0_1.md index b90f5f9..a5d368a 100644 --- a/docs/roadmap/V0_1.md +++ b/docs/roadmap/V0_1.md @@ -37,13 +37,13 @@ The slice includes strict schema validation, representative positive/negative fi ## Slice 2 — Control-plane protocol -**Status:** v0.1 command surface implemented and verified; durable command/query composition remains intentionally partial. +**Status:** v0.1 command/query surface implemented and verified; durable protocol parity is complete locally. ADR-0005 defines the transport-neutral command/query semantics, idempotency scope, fencing/revision authority, recovery behavior, error model, and HTTP/MCP mapping principles. The canonical runtime/application command surface implements all ADR-0005 v0.1 commands, including Agent registration and Session bootstrap, lifecycle/recovery, permission request/decision, cancellation, and idempotent replay. Durable composition now covers the complete ADR-0005 v0.1 command surface, including `RetryTask`, `CancelTask`, and `CancelGoal`. Database-enforced mutation guards preserve receipt atomicity, Goal-versus-Task admission ordering, and cancellation-versus-recovery fencing under the SQLite D1-like reference harness. -Durable read/query support now includes single-resource execution/permission reads, checkpoint and permission-decision history, the pending-human queue, and advisory claimable work. `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain pending. +Durable read/query support now covers the complete ADR-0005 v0.1 query surface, including bounded `ListGoals`, bounded `ListGoalTasks`, and `GetTaskExecutionView`. The execution view returns Task, effective Lease, and latest Checkpoint from one durable database statement; advisory `ListClaimableTasks` still revalidates execution authority atomically at `ClaimTask`. ## Slice 3 — Local reference runtime @@ -63,10 +63,10 @@ Implemented behavior includes: - runtime snapshot rehydration from canonical durable state; - a durable dispatcher that reloads authoritative state for every supported command rather than retaining an in-memory fallback; - atomic durable receipts for the first command loop, including deferred `ClaimTask` receipt construction from the persistence-allocated fencing token; -- durable queries for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`; +- durable queries for the complete ADR-0005 v0.1 surface, including bounded `ListGoals` / `ListGoalTasks`, coherent `GetTaskExecutionView`, execution-resource reads, checkpoint and permission history, pending-human work, and advisory `ListClaimableTasks`; - HTTP E2E evidence for restart-after-claim, restart-after-`HUMAN_REQUIRED`, response-loss replay, competing claims, and monotonic fencing recovery against the SQLite D1-like test harness. -Durable Session/Lease liveness is now covered through HTTP restart E2E: heartbeat does not extend Lease authority, renew preserves the fence, release permits higher-fence recovery, and EndSession durably revokes active Lease authority before recovery. Durable lifecycle/cancellation command parity is complete locally; remaining local work is bounded query parity plus persistence-concurrency hardening and final end-to-end reconciliation. The executed SQLite/D1-like tests do not substitute for deployed Cloudflare verification. +Durable Session/Lease liveness is now covered through HTTP restart E2E: heartbeat does not extend Lease authority, renew preserves the fence, release permits higher-fence recovery, and EndSession durably revokes active Lease authority before recovery. Durable command and query parity is complete locally; remaining local hardening is correctness-focused persistence/concurrency review plus real agent/deployed-reference integration. The executed SQLite/D1-like tests do not substitute for deployed Cloudflare verification. ## Slice 4 — GitHub integration @@ -111,4 +111,4 @@ Add optional human-facing projections such as Google Sheets or a lightweight UI A user can give a goal to a supported real agent, the agent can obtain a **durable** task/context/policy assignment from MindRail, report checkpoints and evidence, continue to the next action without asking the user by default, and escalate only when a policy/decision boundary requires human input. -**Current gap to that condition:** at least one real agent-host integration, the three remaining durable read queries, persistence/deployed-reference-runtime hardening, and real Cloudflare verification are outstanding. The local durable HTTP composition now proves the command-side persistence/restart contract, but it is not yet a real-agent or deployed-Cloudflare product path. +**Current gap to that condition:** at least one real agent-host integration, persistence/deployed-reference-runtime hardening, and real Cloudflare verification are outstanding. The local durable HTTP composition now proves full v0.1 command/query persistence and restart semantics, but it is not yet a real-agent or deployed-Cloudflare product path. diff --git a/scripts/reconcile-durable-query-parity-docs.py b/scripts/reconcile-durable-query-parity-docs.py deleted file mode 100644 index e6f4802..0000000 --- a/scripts/reconcile-durable-query-parity-docs.py +++ /dev/null @@ -1,83 +0,0 @@ -from pathlib import Path - - -def replace_once(path: str, old: str, new: str) -> None: - file = Path(path) - text = file.read_text() - if old not in text: - raise SystemExit(f'target not found in {path}: {old[:120]!r}') - file.write_text(text.replace(old, new, 1)) - - -replace_once( - 'docs/CURRENT_STATE.md', - '**Last reconciled:** 2026-08-29', - '**Last reconciled:** 2026-08-30', -) -replace_once( - 'docs/CURRENT_STATE.md', - '- Explicit durable read ports and application queries are implemented for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`. List queries use bounded deterministic cursor paging. `ListClaimableTasks` includes capability-compatible `ready` Tasks plus `running` Tasks whose prior Lease/Session authority is no longer effective at the authoritative server time; work acquisition still revalidates authority atomically at `ClaimTask`.', - '- Explicit durable read ports and application queries cover the complete ADR-0005 v0.1 query surface: `GetWorkspace`, `ListGoals`, `GetGoal`, `ListGoalTasks`, `GetTask`, `GetTaskExecutionView`, `ListClaimableTasks`, `GetLease`, `GetAgent`, `GetSession`, `ListTaskCheckpoints`, `GetPermissionRequest`, `ListPendingHumanPermissions`, and `ListPermissionDecisions`. List queries use bounded deterministic cursor paging. `ListClaimableTasks` includes capability-compatible `ready` Tasks plus `running` Tasks whose prior Lease/Session authority is no longer effective at authoritative server time; work acquisition still revalidates authority atomically at `ClaimTask`. `GetTaskExecutionView` reads Task, effective Lease, and latest Checkpoint through one D1 statement so the projection is coherent at one database read snapshot.', -) -replace_once( - 'docs/CURRENT_STATE.md', - '- Durable queries `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain explicitly unsupported rather than inferred from retained application state.\n', - '- The locally verified durable application composition now has command and query parity with the complete ADR-0005 v0.1 protocol surface; remaining product gaps are external integration and deployed-reference verification rather than retained in-memory fallbacks.\n', -) -replace_once( - 'docs/CURRENT_STATE.md', - '- Add the remaining bounded durable queries only where required by agent/human workflows.\n', - '', -) -replace_once( - 'docs/CURRENT_STATE.md', - '- Durable task-outcome RED Quality run `33278105807` kept the previous **130 tests green** while both new outcome regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. GREEN run `33278271723` passed full `pnpm check` and coverage after self-cleaning the one-time implementation harness. Review-hardening run `33278367867` then proved persisted FailTask Lease state, lost-response BlockTask replay, durable blocked Checkpoint storage, and complete persisted ResumeTask state. Atomicity run `33278493339` added D1-like mid-batch fault injection and passed **32/32 test files and 133/133 tests** plus coverage at 84.99% statements, 74.15% branches, 96.36% functions, and 86.73% lines; the injected failure rolls back Checkpoint, Task, Lease, and receipt together before an exact retry succeeds. Durable retry/cancellation review hardening culminated in run `33283962742`, which passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines. The regressions cover lost-response replay, `CancelTask` without an effective Lease, `CancelGoal` mid-batch rollback, stale Goal task admission, false-success receipt prevention after a lost CAS, and recovery-`ClaimTask` races against both Task and Goal cancellation.', - '- Durable task-outcome RED Quality run `33278105807` kept the previous **130 tests green** while both new outcome regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. GREEN run `33278271723` passed full `pnpm check` and coverage after self-cleaning the one-time implementation harness. Review-hardening run `33278367867` then proved persisted FailTask Lease state, lost-response BlockTask replay, durable blocked Checkpoint storage, and complete persisted ResumeTask state. Atomicity run `33278493339` added D1-like mid-batch fault injection and passed **32/32 test files and 133/133 tests** plus coverage at 84.99% statements, 74.15% branches, 96.36% functions, and 86.73% lines; the injected failure rolls back Checkpoint, Task, Lease, and receipt together before an exact retry succeeds. Durable retry/cancellation review hardening culminated in run `33283962742`, which passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines. The regressions cover lost-response replay, `CancelTask` without an effective Lease, `CancelGoal` mid-batch rollback, stale Goal task admission, false-success receipt prevention after a lost CAS, and recovery-`ClaimTask` races against both Task and Goal cancellation. Durable query-parity RED Quality run `33284893955` kept those **143 tests green** while exactly three new query regressions failed on the expected `UNSUPPORTED_OPERATION` boundary. Query hardening run `33285112259` then passed **34/34 test files and 146/146 tests**, full `pnpm check`, and coverage at 85.1% statements, 74.51% branches, 96.79% functions, and 86.86% lines after `ListGoals`, `ListGoalTasks`, and the single-statement `GetTaskExecutionView` projection were made durable.', -) - -replace_once( - 'docs/roadmap/V0_1.md', - '**Status:** v0.1 command surface implemented and verified; durable command/query composition remains intentionally partial.', - '**Status:** v0.1 command/query surface implemented and verified; durable protocol parity is complete locally.', -) -replace_once( - 'docs/roadmap/V0_1.md', - 'Durable read/query support now includes single-resource execution/permission reads, checkpoint and permission-decision history, the pending-human queue, and advisory claimable work. `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain pending.', - 'Durable read/query support now covers the complete ADR-0005 v0.1 query surface, including bounded `ListGoals`, bounded `ListGoalTasks`, and `GetTaskExecutionView`. The execution view returns Task, effective Lease, and latest Checkpoint from one durable database statement; advisory `ListClaimableTasks` still revalidates execution authority atomically at `ClaimTask`.', -) -replace_once( - 'docs/roadmap/V0_1.md', - '- durable queries for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`;', - '- durable queries for the complete ADR-0005 v0.1 surface, including bounded `ListGoals` / `ListGoalTasks`, coherent `GetTaskExecutionView`, execution-resource reads, checkpoint and permission history, pending-human work, and advisory `ListClaimableTasks`;', -) -replace_once( - 'docs/roadmap/V0_1.md', - 'Durable Session/Lease liveness is now covered through HTTP restart E2E: heartbeat does not extend Lease authority, renew preserves the fence, release permits higher-fence recovery, and EndSession durably revokes active Lease authority before recovery. Durable lifecycle/cancellation command parity is complete locally; remaining local work is bounded query parity plus persistence-concurrency hardening and final end-to-end reconciliation. The executed SQLite/D1-like tests do not substitute for deployed Cloudflare verification.', - 'Durable Session/Lease liveness is now covered through HTTP restart E2E: heartbeat does not extend Lease authority, renew preserves the fence, release permits higher-fence recovery, and EndSession durably revokes active Lease authority before recovery. Durable command and query parity is complete locally; remaining local hardening is correctness-focused persistence/concurrency review plus real agent/deployed-reference integration. The executed SQLite/D1-like tests do not substitute for deployed Cloudflare verification.', -) -replace_once( - 'docs/roadmap/V0_1.md', - '**Current gap to that condition:** at least one real agent-host integration, the three remaining durable read queries, persistence/deployed-reference-runtime hardening, and real Cloudflare verification are outstanding. The local durable HTTP composition now proves the command-side persistence/restart contract, but it is not yet a real-agent or deployed-Cloudflare product path.', - '**Current gap to that condition:** at least one real agent-host integration, persistence/deployed-reference-runtime hardening, and real Cloudflare verification are outstanding. The local durable HTTP composition now proves full v0.1 command/query persistence and restart semantics, but it is not yet a real-agent or deployed-Cloudflare product path.', -) - -replace_once( - 'CHANGELOG.md', - '- Explicit durable query ports and bounded query support for `GetWorkspace`, `GetGoal`, `GetTask`, `GetLease`, `GetAgent`, `GetSession`, `GetPermissionRequest`, `ListTaskCheckpoints`, `ListPendingHumanPermissions`, `ListPermissionDecisions`, and advisory `ListClaimableTasks`.', - '- Explicit durable query ports and bounded query support for the complete ADR-0005 v0.1 surface: `GetWorkspace`, `ListGoals`, `GetGoal`, `ListGoalTasks`, `GetTask`, `GetTaskExecutionView`, `ListClaimableTasks`, `GetLease`, `GetAgent`, `GetSession`, `ListTaskCheckpoints`, `GetPermissionRequest`, `ListPendingHumanPermissions`, and `ListPermissionDecisions`.', -) -replace_once( - 'CHANGELOG.md', - '- D1 mutation batches now use transaction-aborting guard rows for conditional mutation races. Task creation/retry revalidate an active parent Goal inside the database batch; Goal cancellation terminalizes the Goal only after its cancellable Tasks/Leases and only when no cancellable work remains; claim/cancellation share fencing-counter and Task-state predicates so neither can return success with execution authority beneath cancelled state.', - '- D1 mutation batches now use transaction-aborting guard rows for conditional mutation races. Task creation/retry revalidate an active parent Goal inside the database batch; Goal cancellation terminalizes the Goal only after its cancellable Tasks/Leases and only when no cancellable work remains; claim/cancellation share fencing-counter and Task-state predicates so neither can return success with execution authority beneath cancelled state. `GetTaskExecutionView` now derives Task, effective Lease, and latest Checkpoint in one D1 statement so the read projection cannot mix separate database snapshots.', -) -replace_once( - 'CHANGELOG.md', - '- Durable retry/cancellation hardening run `33283962742` passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines after transaction-CAS, stale Goal admission, no-Lease cancellation, mid-batch rollback, false-receipt, and cancellation-versus-recovery claim regressions were made green.', - '- Durable retry/cancellation hardening run `33283962742` passed **33/33 test files and 143/143 tests**, full `pnpm check`, and coverage at 85.0% statements, 74.3% branches, 96.51% functions, and 86.71% lines after transaction-CAS, stale Goal admission, no-Lease cancellation, mid-batch rollback, false-receipt, and cancellation-versus-recovery claim regressions were made green. Durable query-parity RED Quality run `33284893955` preserved those **143 passing tests** while exactly three new query regressions failed on `UNSUPPORTED_OPERATION`; hardening run `33285112259` then passed **34/34 test files and 146/146 tests**, full `pnpm check`, and coverage at 85.1% statements, 74.51% branches, 96.79% functions, and 86.86% lines.', -) -replace_once( - 'CHANGELOG.md', - '- The durable dispatcher supports the complete ADR-0005 v0.1 command surface. The remaining explicit durable application gaps are read-side: `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView`.\n- Durable application queries `ListGoals`, `ListGoalTasks`, and `GetTaskExecutionView` remain explicitly unsupported.\n', - '- The durable dispatcher supports the complete ADR-0005 v0.1 command and query surface against the local SQLite D1-like reference harness; this does not imply deployed Cloudflare verification.\n', -) From c642f2657762c46e9e9eee7c165c79f2c18f5710 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 30 Aug 2026 06:17:47 +0500 Subject: [PATCH 16/16] chore: trigger final query parity quality