You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by "me2seeks (via GitHub)" <gi...@apache.org> on 2026/08/30 04:15:09 UTC
[PR] feat(runtime-host): query durable task mutations [maka]
me2seeks opened a new pull request, #4239:
URL: https://github.com/apache/maka/pull/4239
## Summary
- add a Host-owned, tool-call-correlated Task mutation projection from the durable Task Ledger
- expose a closed `task.mutation.query` operation with bounded whole-presentation paging
- freeze continuation reads at a sequence/event watermark, survive Host restart and appends, and return `history_changed` after purge/replacement
- fail closed on non-canonical ledger history and keep producer text redacted
## Scope
This is the Runtime Host protocol/storage vertical slice for #4179. It does not add CLI/TUI/Desktop rendering and does not persist a second presentation authority.
## Validation
- `@maka/storage`: 981 passed, 9 skipped, 0 failed
- `@maka/runtime-host`: 1393 passed, 9 skipped, 0 failed before the final main rebase
- final-head protocol/projection: 12 passed
- final-head dual-client restart, append-freeze/purge, cursor tamper, and Session copy integration tests: passed
- two independent exact-head reviews: 0 blocking
Part of #4179.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(runtime-host): query durable task mutations [maka]
Posted by "me2seeks (via GitHub)" <gi...@apache.org>.
me2seeks commented on code in PR #4239:
URL: https://github.com/apache/maka/pull/4239#discussion_r3889401325
##########
packages/runtime-host/src/protocol/index.ts:
##########
@@ -95,7 +96,9 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1 as const;
export const RUNTIME_HOST_PROTOCOL_VERSION = 0 as const;
// Increment when the same protocol version no longer guarantees safe Client-Host
// interoperability. Mismatches are rejected before domain commands are admitted.
-export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 72 as const;
+export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 73 as const;
Review Comment:
Fixed in dd6917b8c. The stack is rebased onto main's epoch 74 and task.mutation.query now uses the fresh epoch 75. Exact-head Runtime Host and Storage suites passed, and the merge-tree against current main is clean.
##########
packages/runtime-host/src/protocol/task-mutation.ts:
##########
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TASK_EVIDENCE_MAX_CHARS,
+ TASK_SUBJECT_MAX_CHARS,
+ canTransitionTaskStatus,
+ isSafeTaskId,
+ isTaskKey,
+ isTaskStatus,
+ normalizeTaskEvidenceText,
+ normalizeTaskSubject,
+ sanitizeTaskLedgerTask,
+ type Task,
+ type TaskStatus,
+} from '@maka/core/task-ledger';
+import { requireEntityId, requireExactRecord, requireRecord } from './codec.js';
+import { invalidProtocolFrame } from './errors.js';
+import { defineOperation } from './operation-spec.js';
+
+export const TASK_MUTATION_QUERY_MAX_CORRELATIONS = 128;
+export const TASK_MUTATION_PAGE_MAX_ITEMS = 128;
+export const TASK_MUTATION_PAGE_MAX_BYTES = 256 * 1024;
+export const TASK_MUTATION_CURSOR_MAX_BYTES = 1024;
+
+const QUERY_ERRORS = [
+ 'host_not_ready',
+ 'host_draining',
+ 'operation_unavailable',
+ 'invalid_request',
+ 'not_found',
+ 'internal_failure',
+] as const;
+
+export type TaskMutationRevision = `sha256:${string}`;
+
+export interface TaskMutationCorrelation {
+ readonly turnId: string;
+ readonly toolCallId: string;
+}
+
+export interface TaskMutationChange {
+ readonly taskId: string;
+ readonly key: string;
+ readonly subject: string;
+ readonly previousStatus?: TaskStatus;
+ readonly nextStatus: TaskStatus;
+ readonly reason?: string;
+ readonly evidence?: string;
+}
+
+export interface TaskMutationPresentation {
+ readonly operation: 'create' | 'update';
+ readonly correlation: TaskMutationCorrelation;
+ readonly changes: readonly TaskMutationChange[];
+}
+
+export type TaskMutationLookup =
+ | {
+ readonly kind: 'found';
+ readonly correlation: TaskMutationCorrelation;
+ readonly presentation: TaskMutationPresentation;
+ }
+ | {
+ readonly kind: 'not_found' | 'incompatible';
+ readonly correlation: TaskMutationCorrelation;
+ };
+
+export type TaskMutationQueryInput =
+ | {
+ readonly kind: 'start';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ }
+ | {
+ readonly kind: 'continue';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ readonly revision: TaskMutationRevision;
+ readonly cursor: string;
+ };
+
+export type TaskMutationQueryResult =
+ | {
+ readonly kind: 'page';
+ readonly sessionId: string;
+ readonly revision: TaskMutationRevision;
+ readonly lookups: readonly TaskMutationLookup[];
+ readonly nextCursor: string | null;
+ }
+ | {
+ readonly kind: 'history_changed';
+ readonly expected: TaskMutationRevision;
+ readonly actual: TaskMutationRevision;
+ };
+
+export const TASK_MUTATION_OPERATION_SPECS = {
+ 'task.mutation.query': defineOperation<
+ TaskMutationQueryInput,
+ TaskMutationQueryResult,
+ (typeof QUERY_ERRORS)[number]
+ >({
+ mode: 'query',
+ availability: 'ready',
+ errors: QUERY_ERRORS,
+ decodeInput: decodeTaskMutationQueryInput,
+ decodeOutput: decodeTaskMutationQueryResult,
+ }),
+} as const;
+
+export function decodeTaskMutationQueryInput(value: unknown): TaskMutationQueryInput {
+ const record = requireRecord(value, 'task mutation query input');
+ if (record.kind === 'start') {
+ const input = requireExactRecord(record, 'task mutation query start input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ ]);
+ return {
+ kind: 'start',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ };
+ }
+ if (record.kind === 'continue') {
+ const input = requireExactRecord(record, 'task mutation query continuation input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ 'revision',
+ 'cursor',
+ ]);
+ return {
+ kind: 'continue',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ revision: taskMutationRevision(input.revision, 'task mutation revision'),
+ cursor: taskMutationCursor(input.cursor),
+ };
+ }
+ throw invalidProtocolFrame('Invalid task mutation query kind');
+}
+
+export function decodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'decode');
+}
+
+export function encodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'encode');
+}
+
+function taskMutationQueryResult(
+ value: unknown,
+ direction: 'encode' | 'decode',
+): TaskMutationQueryResult {
+ const record = requireRecord(value, 'task mutation query result');
+ if (record.kind === 'history_changed') {
+ const changed = requireExactRecord(record, 'task mutation history changed result', [
+ 'kind',
+ 'expected',
+ 'actual',
+ ]);
+ return {
+ kind: 'history_changed',
+ expected: taskMutationRevision(changed.expected, 'expected task mutation revision'),
+ actual: taskMutationRevision(changed.actual, 'actual task mutation revision'),
+ };
+ }
+ if (record.kind !== 'page') throw invalidProtocolFrame('Invalid task mutation query result kind');
+ const page = requireExactRecord(record, 'task mutation query page', [
+ 'kind',
+ 'sessionId',
+ 'revision',
+ 'lookups',
+ 'nextCursor',
+ ]);
+ if (!Array.isArray(page.lookups) || page.lookups.length > TASK_MUTATION_PAGE_MAX_ITEMS) {
+ throw invalidProtocolFrame('Task mutation page exceeds item limit');
+ }
+ const decoded: TaskMutationQueryResult = {
+ kind: 'page',
+ sessionId: requireEntityId(page.sessionId, 'sessionId'),
+ revision: taskMutationRevision(page.revision, 'task mutation revision'),
+ lookups: page.lookups.map((lookup) => taskMutationLookup(lookup, direction)),
+ nextCursor: page.nextCursor === null ? null : taskMutationCursor(page.nextCursor),
+ };
+ if (jsonByteLength(decoded) > TASK_MUTATION_PAGE_MAX_BYTES) {
+ throw invalidProtocolFrame('Task mutation page exceeds byte limit');
+ }
+ return decoded;
+}
+
+function taskMutationCorrelations(value: unknown): readonly TaskMutationCorrelation[] {
+ if (
+ !Array.isArray(value) ||
+ value.length === 0 ||
+ value.length > TASK_MUTATION_QUERY_MAX_CORRELATIONS
+ ) {
+ throw invalidProtocolFrame('Invalid task mutation correlations');
+ }
+ const correlations = value.map(taskMutationCorrelation);
+ const unique = new Set(correlations.map(correlationKey));
+ if (unique.size !== correlations.length) {
+ throw invalidProtocolFrame('Duplicate task mutation correlation');
+ }
+ return correlations;
+}
+
+function taskMutationCorrelation(value: unknown): TaskMutationCorrelation {
+ const correlation = requireExactRecord(value, 'task mutation correlation', [
+ 'turnId',
+ 'toolCallId',
+ ]);
+ return {
+ turnId: requireEntityId(correlation.turnId, 'turnId'),
+ toolCallId: requireEntityId(correlation.toolCallId, 'toolCallId'),
Review Comment:
Fixed in dd6917b8c. toolCallId now uses the bounded opaque ID decoder while turnId remains an entity ID. Protocol and real execution-host regressions cover nested task_create/task_update correlations and invalid bounds.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(runtime-host): query durable task mutations [maka]
Posted by "me2seeks (via GitHub)" <gi...@apache.org>.
me2seeks commented on code in PR #4239:
URL: https://github.com/apache/maka/pull/4239#discussion_r3889847044
##########
packages/runtime-host/src/protocol/task-mutation.ts:
##########
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TASK_EVIDENCE_MAX_CHARS,
+ TASK_SUBJECT_MAX_CHARS,
+ canTransitionTaskStatus,
+ isSafeTaskId,
+ isTaskKey,
+ isTaskStatus,
+ normalizeTaskEvidenceText,
+ normalizeTaskSubject,
+ sanitizeTaskLedgerTask,
+ type Task,
+ type TaskStatus,
+} from '@maka/core/task-ledger';
+import { requireEntityId, requireExactRecord, requireId, requireRecord } from './codec.js';
+import { invalidProtocolFrame } from './errors.js';
+import { defineOperation } from './operation-spec.js';
+
+export const TASK_MUTATION_QUERY_MAX_CORRELATIONS = 128;
+export const TASK_MUTATION_PAGE_MAX_ITEMS = 128;
+export const TASK_MUTATION_PAGE_MAX_BYTES = 256 * 1024;
+export const TASK_MUTATION_CURSOR_MAX_BYTES = 1024;
+
+const QUERY_ERRORS = [
+ 'host_not_ready',
+ 'host_draining',
+ 'operation_unavailable',
+ 'invalid_request',
+ 'not_found',
+ 'internal_failure',
+] as const;
+
+export type TaskMutationRevision = `sha256:${string}`;
+
+export interface TaskMutationCorrelation {
+ readonly turnId: string;
+ readonly toolCallId: string;
+}
+
+export interface TaskMutationChange {
+ readonly taskId: string;
+ readonly key: string;
+ readonly subject: string;
+ readonly previousStatus?: TaskStatus;
+ readonly nextStatus: TaskStatus;
+ readonly reason?: string;
+ readonly evidence?: string;
+}
+
+export interface TaskMutationPresentation {
+ readonly operation: 'create' | 'update';
+ readonly correlation: TaskMutationCorrelation;
+ readonly changes: readonly TaskMutationChange[];
+}
+
+export type TaskMutationLookup =
+ | {
+ readonly kind: 'found';
+ readonly correlation: TaskMutationCorrelation;
+ readonly presentation: TaskMutationPresentation;
+ }
+ | {
+ readonly kind: 'not_found' | 'incompatible';
+ readonly correlation: TaskMutationCorrelation;
+ };
+
+export type TaskMutationQueryInput =
+ | {
+ readonly kind: 'start';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ }
+ | {
+ readonly kind: 'continue';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ readonly revision: TaskMutationRevision;
+ readonly cursor: string;
+ };
+
+export type TaskMutationQueryResult =
+ | {
+ readonly kind: 'page';
+ readonly sessionId: string;
+ readonly revision: TaskMutationRevision;
+ readonly lookups: readonly TaskMutationLookup[];
+ readonly nextCursor: string | null;
+ }
+ | {
+ readonly kind: 'history_changed';
+ readonly expected: TaskMutationRevision;
+ readonly actual: TaskMutationRevision;
+ };
+
+export const TASK_MUTATION_OPERATION_SPECS = {
+ 'task.mutation.query': defineOperation<
+ TaskMutationQueryInput,
+ TaskMutationQueryResult,
+ (typeof QUERY_ERRORS)[number]
+ >({
+ mode: 'query',
+ availability: 'ready',
+ errors: QUERY_ERRORS,
+ decodeInput: decodeTaskMutationQueryInput,
+ decodeOutput: decodeTaskMutationQueryResult,
+ }),
+} as const;
+
+export function decodeTaskMutationQueryInput(value: unknown): TaskMutationQueryInput {
+ const record = requireRecord(value, 'task mutation query input');
+ if (record.kind === 'start') {
+ const input = requireExactRecord(record, 'task mutation query start input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ ]);
+ return {
+ kind: 'start',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ };
+ }
+ if (record.kind === 'continue') {
+ const input = requireExactRecord(record, 'task mutation query continuation input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ 'revision',
+ 'cursor',
+ ]);
+ return {
+ kind: 'continue',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ revision: taskMutationRevision(input.revision, 'task mutation revision'),
+ cursor: taskMutationCursor(input.cursor),
+ };
+ }
+ throw invalidProtocolFrame('Invalid task mutation query kind');
+}
+
+export function decodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'decode');
+}
+
+export function encodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'encode');
+}
+
+function taskMutationQueryResult(
+ value: unknown,
+ direction: 'encode' | 'decode',
+): TaskMutationQueryResult {
+ const record = requireRecord(value, 'task mutation query result');
+ if (record.kind === 'history_changed') {
+ const changed = requireExactRecord(record, 'task mutation history changed result', [
+ 'kind',
+ 'expected',
+ 'actual',
+ ]);
+ return {
+ kind: 'history_changed',
+ expected: taskMutationRevision(changed.expected, 'expected task mutation revision'),
+ actual: taskMutationRevision(changed.actual, 'actual task mutation revision'),
+ };
+ }
+ if (record.kind !== 'page') throw invalidProtocolFrame('Invalid task mutation query result kind');
+ const page = requireExactRecord(record, 'task mutation query page', [
+ 'kind',
+ 'sessionId',
+ 'revision',
+ 'lookups',
+ 'nextCursor',
+ ]);
+ if (!Array.isArray(page.lookups) || page.lookups.length > TASK_MUTATION_PAGE_MAX_ITEMS) {
+ throw invalidProtocolFrame('Task mutation page exceeds item limit');
+ }
+ const decoded: TaskMutationQueryResult = {
+ kind: 'page',
+ sessionId: requireEntityId(page.sessionId, 'sessionId'),
+ revision: taskMutationRevision(page.revision, 'task mutation revision'),
+ lookups: page.lookups.map((lookup) => taskMutationLookup(lookup, direction)),
+ nextCursor: page.nextCursor === null ? null : taskMutationCursor(page.nextCursor),
+ };
+ if (jsonByteLength(decoded) > TASK_MUTATION_PAGE_MAX_BYTES) {
+ throw invalidProtocolFrame('Task mutation page exceeds byte limit');
+ }
+ return decoded;
+}
+
+function taskMutationCorrelations(value: unknown): readonly TaskMutationCorrelation[] {
+ if (
+ !Array.isArray(value) ||
+ value.length === 0 ||
+ value.length > TASK_MUTATION_QUERY_MAX_CORRELATIONS
+ ) {
+ throw invalidProtocolFrame('Invalid task mutation correlations');
+ }
+ const correlations = value.map(taskMutationCorrelation);
+ const unique = new Set(correlations.map(correlationKey));
+ if (unique.size !== correlations.length) {
+ throw invalidProtocolFrame('Duplicate task mutation correlation');
+ }
+ return correlations;
+}
+
+function taskMutationCorrelation(value: unknown): TaskMutationCorrelation {
+ const correlation = requireExactRecord(value, 'task mutation correlation', [
+ 'turnId',
+ 'toolCallId',
+ ]);
+ return {
+ turnId: requireEntityId(correlation.turnId, 'turnId'),
+ toolCallId: requireId(correlation.toolCallId, 'toolCallId'),
Review Comment:
Fixed on 7e564561c. Runtime now preserves fitting nested IDs and deterministically hashes only oversized parent/child tuples to a bounded ID while retaining the full parentToolCallId separately. The Host query also accepts legacy opaque IDs up to a JSON-encoded 2 KiB, with aggregate/input byte caps. Added boundary, Unicode/collision, real Code Mode producer -> Task Ledger -> Host query, and cursor/page regressions.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(runtime-host): query durable task mutations [maka]
Posted by "me2seeks (via GitHub)" <gi...@apache.org>.
me2seeks closed pull request #4239: feat(runtime-host): query durable task mutations
URL: https://github.com/apache/maka/pull/4239
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(runtime-host): query durable task mutations [maka]
Posted by "M4n5ter (via GitHub)" <gi...@apache.org>.
M4n5ter commented on code in PR #4239:
URL: https://github.com/apache/maka/pull/4239#discussion_r3888637460
##########
packages/runtime-host/src/protocol/task-mutation.ts:
##########
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TASK_EVIDENCE_MAX_CHARS,
+ TASK_SUBJECT_MAX_CHARS,
+ canTransitionTaskStatus,
+ isSafeTaskId,
+ isTaskKey,
+ isTaskStatus,
+ normalizeTaskEvidenceText,
+ normalizeTaskSubject,
+ sanitizeTaskLedgerTask,
+ type Task,
+ type TaskStatus,
+} from '@maka/core/task-ledger';
+import { requireEntityId, requireExactRecord, requireRecord } from './codec.js';
+import { invalidProtocolFrame } from './errors.js';
+import { defineOperation } from './operation-spec.js';
+
+export const TASK_MUTATION_QUERY_MAX_CORRELATIONS = 128;
+export const TASK_MUTATION_PAGE_MAX_ITEMS = 128;
+export const TASK_MUTATION_PAGE_MAX_BYTES = 256 * 1024;
+export const TASK_MUTATION_CURSOR_MAX_BYTES = 1024;
+
+const QUERY_ERRORS = [
+ 'host_not_ready',
+ 'host_draining',
+ 'operation_unavailable',
+ 'invalid_request',
+ 'not_found',
+ 'internal_failure',
+] as const;
+
+export type TaskMutationRevision = `sha256:${string}`;
+
+export interface TaskMutationCorrelation {
+ readonly turnId: string;
+ readonly toolCallId: string;
+}
+
+export interface TaskMutationChange {
+ readonly taskId: string;
+ readonly key: string;
+ readonly subject: string;
+ readonly previousStatus?: TaskStatus;
+ readonly nextStatus: TaskStatus;
+ readonly reason?: string;
+ readonly evidence?: string;
+}
+
+export interface TaskMutationPresentation {
+ readonly operation: 'create' | 'update';
+ readonly correlation: TaskMutationCorrelation;
+ readonly changes: readonly TaskMutationChange[];
+}
+
+export type TaskMutationLookup =
+ | {
+ readonly kind: 'found';
+ readonly correlation: TaskMutationCorrelation;
+ readonly presentation: TaskMutationPresentation;
+ }
+ | {
+ readonly kind: 'not_found' | 'incompatible';
+ readonly correlation: TaskMutationCorrelation;
+ };
+
+export type TaskMutationQueryInput =
+ | {
+ readonly kind: 'start';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ }
+ | {
+ readonly kind: 'continue';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ readonly revision: TaskMutationRevision;
+ readonly cursor: string;
+ };
+
+export type TaskMutationQueryResult =
+ | {
+ readonly kind: 'page';
+ readonly sessionId: string;
+ readonly revision: TaskMutationRevision;
+ readonly lookups: readonly TaskMutationLookup[];
+ readonly nextCursor: string | null;
+ }
+ | {
+ readonly kind: 'history_changed';
+ readonly expected: TaskMutationRevision;
+ readonly actual: TaskMutationRevision;
+ };
+
+export const TASK_MUTATION_OPERATION_SPECS = {
+ 'task.mutation.query': defineOperation<
+ TaskMutationQueryInput,
+ TaskMutationQueryResult,
+ (typeof QUERY_ERRORS)[number]
+ >({
+ mode: 'query',
+ availability: 'ready',
+ errors: QUERY_ERRORS,
+ decodeInput: decodeTaskMutationQueryInput,
+ decodeOutput: decodeTaskMutationQueryResult,
+ }),
+} as const;
+
+export function decodeTaskMutationQueryInput(value: unknown): TaskMutationQueryInput {
+ const record = requireRecord(value, 'task mutation query input');
+ if (record.kind === 'start') {
+ const input = requireExactRecord(record, 'task mutation query start input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ ]);
+ return {
+ kind: 'start',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ };
+ }
+ if (record.kind === 'continue') {
+ const input = requireExactRecord(record, 'task mutation query continuation input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ 'revision',
+ 'cursor',
+ ]);
+ return {
+ kind: 'continue',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ revision: taskMutationRevision(input.revision, 'task mutation revision'),
+ cursor: taskMutationCursor(input.cursor),
+ };
+ }
+ throw invalidProtocolFrame('Invalid task mutation query kind');
+}
+
+export function decodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'decode');
+}
+
+export function encodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'encode');
+}
+
+function taskMutationQueryResult(
+ value: unknown,
+ direction: 'encode' | 'decode',
+): TaskMutationQueryResult {
+ const record = requireRecord(value, 'task mutation query result');
+ if (record.kind === 'history_changed') {
+ const changed = requireExactRecord(record, 'task mutation history changed result', [
+ 'kind',
+ 'expected',
+ 'actual',
+ ]);
+ return {
+ kind: 'history_changed',
+ expected: taskMutationRevision(changed.expected, 'expected task mutation revision'),
+ actual: taskMutationRevision(changed.actual, 'actual task mutation revision'),
+ };
+ }
+ if (record.kind !== 'page') throw invalidProtocolFrame('Invalid task mutation query result kind');
+ const page = requireExactRecord(record, 'task mutation query page', [
+ 'kind',
+ 'sessionId',
+ 'revision',
+ 'lookups',
+ 'nextCursor',
+ ]);
+ if (!Array.isArray(page.lookups) || page.lookups.length > TASK_MUTATION_PAGE_MAX_ITEMS) {
+ throw invalidProtocolFrame('Task mutation page exceeds item limit');
+ }
+ const decoded: TaskMutationQueryResult = {
+ kind: 'page',
+ sessionId: requireEntityId(page.sessionId, 'sessionId'),
+ revision: taskMutationRevision(page.revision, 'task mutation revision'),
+ lookups: page.lookups.map((lookup) => taskMutationLookup(lookup, direction)),
+ nextCursor: page.nextCursor === null ? null : taskMutationCursor(page.nextCursor),
+ };
+ if (jsonByteLength(decoded) > TASK_MUTATION_PAGE_MAX_BYTES) {
+ throw invalidProtocolFrame('Task mutation page exceeds byte limit');
+ }
+ return decoded;
+}
+
+function taskMutationCorrelations(value: unknown): readonly TaskMutationCorrelation[] {
+ if (
+ !Array.isArray(value) ||
+ value.length === 0 ||
+ value.length > TASK_MUTATION_QUERY_MAX_CORRELATIONS
+ ) {
+ throw invalidProtocolFrame('Invalid task mutation correlations');
+ }
+ const correlations = value.map(taskMutationCorrelation);
+ const unique = new Set(correlations.map(correlationKey));
+ if (unique.size !== correlations.length) {
+ throw invalidProtocolFrame('Duplicate task mutation correlation');
+ }
+ return correlations;
+}
+
+function taskMutationCorrelation(value: unknown): TaskMutationCorrelation {
+ const correlation = requireExactRecord(value, 'task mutation correlation', [
+ 'turnId',
+ 'toolCallId',
+ ]);
+ return {
+ turnId: requireEntityId(correlation.turnId, 'turnId'),
+ toolCallId: requireEntityId(correlation.toolCallId, 'toolCallId'),
Review Comment:
**[P1] Accept the tool-call identifiers produced by nested Code Mode calls**
`requireEntityId` permits only `[A-Za-z0-9_-]`, but the production nested-tool path constructs identifiers as `${parent}:nested:${uuid}` and persists that exact value in both the transcript and Task Ledger refs. Ordinary tools are Code Mode-nestable by default, so nested `task_create` and `task_update` are supported paths. A decoder probe using `exec-1:nested:123e4567-e89b-12d3-a456-426614174000` fails here with `RuntimeHostProtocolError: Invalid toolCallId`; the query therefore cannot address the durable mutation it is intended to render.
Please decode this field with the actual bounded opaque tool-call identity contract, including Code Mode delimiters, consistently across transcript, ledger, and protocol. Add query regressions for nested `task_create` and `task_update` correlations.
---
> **Automated review notice:** This comment was posted by an automated review agent operated by M4n5ter. It is not an independent human review and does not replace one.
##########
packages/runtime-host/src/protocol/index.ts:
##########
@@ -95,7 +96,9 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1 as const;
export const RUNTIME_HOST_PROTOCOL_VERSION = 0 as const;
// Increment when the same protocol version no longer guarantees safe Client-Host
// interoperability. Mismatches are rejected before domain commands are admitted.
-export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 72 as const;
+export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 73 as const;
Review Comment:
**[P1] Allocate a fresh compatibility epoch after rebasing**
Current `main` (`c33617fd8b2a2f6f0d34f053b19428501e9c9bdb`) already uses epoch 73 for the incompatible Host-owned transcript Turn-range boundary, while this head was forked from epoch 72 and independently uses 73 for `task.mutation.query`. `git merge-tree --write-tree origin/main HEAD` reports a conflict here. Keeping 73 after resolving the text would give two incompatible wire contracts the same compatibility identity and also fail the repository's strict epoch-increase guard.
Please rebase the stack and assign a value strictly greater than the then-current `main` epoch (currently at least 74), then rerun the exact-head and merge-result checks.
---
> **Automated review notice:** This comment was posted by an automated review agent operated by M4n5ter. It is not an independent human review and does not replace one.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(runtime-host): query durable task mutations [maka]
Posted by "Astro-Han (via GitHub)" <gi...@apache.org>.
Astro-Han commented on code in PR #4239:
URL: https://github.com/apache/maka/pull/4239#discussion_r3889728713
##########
packages/runtime-host/src/protocol/task-mutation.ts:
##########
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ TASK_EVIDENCE_MAX_CHARS,
+ TASK_SUBJECT_MAX_CHARS,
+ canTransitionTaskStatus,
+ isSafeTaskId,
+ isTaskKey,
+ isTaskStatus,
+ normalizeTaskEvidenceText,
+ normalizeTaskSubject,
+ sanitizeTaskLedgerTask,
+ type Task,
+ type TaskStatus,
+} from '@maka/core/task-ledger';
+import { requireEntityId, requireExactRecord, requireId, requireRecord } from './codec.js';
+import { invalidProtocolFrame } from './errors.js';
+import { defineOperation } from './operation-spec.js';
+
+export const TASK_MUTATION_QUERY_MAX_CORRELATIONS = 128;
+export const TASK_MUTATION_PAGE_MAX_ITEMS = 128;
+export const TASK_MUTATION_PAGE_MAX_BYTES = 256 * 1024;
+export const TASK_MUTATION_CURSOR_MAX_BYTES = 1024;
+
+const QUERY_ERRORS = [
+ 'host_not_ready',
+ 'host_draining',
+ 'operation_unavailable',
+ 'invalid_request',
+ 'not_found',
+ 'internal_failure',
+] as const;
+
+export type TaskMutationRevision = `sha256:${string}`;
+
+export interface TaskMutationCorrelation {
+ readonly turnId: string;
+ readonly toolCallId: string;
+}
+
+export interface TaskMutationChange {
+ readonly taskId: string;
+ readonly key: string;
+ readonly subject: string;
+ readonly previousStatus?: TaskStatus;
+ readonly nextStatus: TaskStatus;
+ readonly reason?: string;
+ readonly evidence?: string;
+}
+
+export interface TaskMutationPresentation {
+ readonly operation: 'create' | 'update';
+ readonly correlation: TaskMutationCorrelation;
+ readonly changes: readonly TaskMutationChange[];
+}
+
+export type TaskMutationLookup =
+ | {
+ readonly kind: 'found';
+ readonly correlation: TaskMutationCorrelation;
+ readonly presentation: TaskMutationPresentation;
+ }
+ | {
+ readonly kind: 'not_found' | 'incompatible';
+ readonly correlation: TaskMutationCorrelation;
+ };
+
+export type TaskMutationQueryInput =
+ | {
+ readonly kind: 'start';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ }
+ | {
+ readonly kind: 'continue';
+ readonly sessionId: string;
+ readonly correlations: readonly TaskMutationCorrelation[];
+ readonly revision: TaskMutationRevision;
+ readonly cursor: string;
+ };
+
+export type TaskMutationQueryResult =
+ | {
+ readonly kind: 'page';
+ readonly sessionId: string;
+ readonly revision: TaskMutationRevision;
+ readonly lookups: readonly TaskMutationLookup[];
+ readonly nextCursor: string | null;
+ }
+ | {
+ readonly kind: 'history_changed';
+ readonly expected: TaskMutationRevision;
+ readonly actual: TaskMutationRevision;
+ };
+
+export const TASK_MUTATION_OPERATION_SPECS = {
+ 'task.mutation.query': defineOperation<
+ TaskMutationQueryInput,
+ TaskMutationQueryResult,
+ (typeof QUERY_ERRORS)[number]
+ >({
+ mode: 'query',
+ availability: 'ready',
+ errors: QUERY_ERRORS,
+ decodeInput: decodeTaskMutationQueryInput,
+ decodeOutput: decodeTaskMutationQueryResult,
+ }),
+} as const;
+
+export function decodeTaskMutationQueryInput(value: unknown): TaskMutationQueryInput {
+ const record = requireRecord(value, 'task mutation query input');
+ if (record.kind === 'start') {
+ const input = requireExactRecord(record, 'task mutation query start input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ ]);
+ return {
+ kind: 'start',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ };
+ }
+ if (record.kind === 'continue') {
+ const input = requireExactRecord(record, 'task mutation query continuation input', [
+ 'kind',
+ 'sessionId',
+ 'correlations',
+ 'revision',
+ 'cursor',
+ ]);
+ return {
+ kind: 'continue',
+ sessionId: requireEntityId(input.sessionId, 'sessionId'),
+ correlations: taskMutationCorrelations(input.correlations),
+ revision: taskMutationRevision(input.revision, 'task mutation revision'),
+ cursor: taskMutationCursor(input.cursor),
+ };
+ }
+ throw invalidProtocolFrame('Invalid task mutation query kind');
+}
+
+export function decodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'decode');
+}
+
+export function encodeTaskMutationQueryResult(value: unknown): TaskMutationQueryResult {
+ return taskMutationQueryResult(value, 'encode');
+}
+
+function taskMutationQueryResult(
+ value: unknown,
+ direction: 'encode' | 'decode',
+): TaskMutationQueryResult {
+ const record = requireRecord(value, 'task mutation query result');
+ if (record.kind === 'history_changed') {
+ const changed = requireExactRecord(record, 'task mutation history changed result', [
+ 'kind',
+ 'expected',
+ 'actual',
+ ]);
+ return {
+ kind: 'history_changed',
+ expected: taskMutationRevision(changed.expected, 'expected task mutation revision'),
+ actual: taskMutationRevision(changed.actual, 'actual task mutation revision'),
+ };
+ }
+ if (record.kind !== 'page') throw invalidProtocolFrame('Invalid task mutation query result kind');
+ const page = requireExactRecord(record, 'task mutation query page', [
+ 'kind',
+ 'sessionId',
+ 'revision',
+ 'lookups',
+ 'nextCursor',
+ ]);
+ if (!Array.isArray(page.lookups) || page.lookups.length > TASK_MUTATION_PAGE_MAX_ITEMS) {
+ throw invalidProtocolFrame('Task mutation page exceeds item limit');
+ }
+ const decoded: TaskMutationQueryResult = {
+ kind: 'page',
+ sessionId: requireEntityId(page.sessionId, 'sessionId'),
+ revision: taskMutationRevision(page.revision, 'task mutation revision'),
+ lookups: page.lookups.map((lookup) => taskMutationLookup(lookup, direction)),
+ nextCursor: page.nextCursor === null ? null : taskMutationCursor(page.nextCursor),
+ };
+ if (jsonByteLength(decoded) > TASK_MUTATION_PAGE_MAX_BYTES) {
+ throw invalidProtocolFrame('Task mutation page exceeds byte limit');
+ }
+ return decoded;
+}
+
+function taskMutationCorrelations(value: unknown): readonly TaskMutationCorrelation[] {
+ if (
+ !Array.isArray(value) ||
+ value.length === 0 ||
+ value.length > TASK_MUTATION_QUERY_MAX_CORRELATIONS
+ ) {
+ throw invalidProtocolFrame('Invalid task mutation correlations');
+ }
+ const correlations = value.map(taskMutationCorrelation);
+ const unique = new Set(correlations.map(correlationKey));
+ if (unique.size !== correlations.length) {
+ throw invalidProtocolFrame('Duplicate task mutation correlation');
+ }
+ return correlations;
+}
+
+function taskMutationCorrelation(value: unknown): TaskMutationCorrelation {
+ const correlation = requireExactRecord(value, 'task mutation correlation', [
+ 'turnId',
+ 'toolCallId',
+ ]);
+ return {
+ turnId: requireEntityId(correlation.turnId, 'turnId'),
+ toolCallId: requireId(correlation.toolCallId, 'toolCallId'),
Review Comment:
[P2] Suggestion (category ① — normal nested Code Mode task-tool path): could this decoder accept the full opaque ID contract that Runtime can actually persist? `requireId()` still caps the value at 128 characters, while `ai-sdk-backend.ts` constructs a nested ID as `${parentToolCallId}:nested:${newId()}` and the provider-supplied parent has no matching 128-character admission limit. A valid parent of roughly 84+ characters therefore produces a durable `task_create`/`task_update` correlation that this query rejects, so the mutation succeeds but its history is unqueryable. I suggest either bounding the producer while reserving suffix space or defining one larger opaque-ID limit shared by Runtime, ledger, transcript, and Host protocol, with a long-parent nested regression. Please push back if a provider-entry invariant already guarantees a smaller parent ID.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
Re: [PR] feat(runtime-host): query durable task mutations [maka]
Posted by "me2seeks (via GitHub)" <gi...@apache.org>.
me2seeks commented on PR #4239:
URL: https://github.com/apache/maka/pull/4239#issuecomment-5481523488
Closing as superseded by #4338 and the merged SessionTodo cutover in #4351. The product no longer exposes durable Task mutation history: SessionTodo is a Host-owned current snapshot with explicit todo_read/todo_write, no revision, cursor, watermark, mutation timeline, or Task identity.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org