Skip to content
6 changes: 5 additions & 1 deletion apps/desktop/src/main/runtime-host-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import { createHash, randomUUID } from "node:crypto";
import type { AttachmentRef, ShellRunUpdate } from "@maka/core/events";
import type { PlanSessionState, PlanUserControlInput } from "@maka/core/plan";
import {
decodeStoredMessage,
decodeStoredMessage as decodePersistedStoredMessage,
type StoredMessage,
type TurnRecord,
} from "@maka/core/session";
import { markPersisted } from "@maka/core/persisted-value";
import type { Task } from "@maka/core/task-ledger";

import type {
ConnectionCatalogSnapshot,
ConnectionVersionBasis,
Expand Down Expand Up @@ -131,6 +133,8 @@ import {
type WorkspaceProjection,
} from "@maka/runtime-host/protocol";

const decodeStoredMessage = (value: unknown): StoredMessage =>
decodePersistedStoredMessage(markPersisted<StoredMessage>(value));
const MAX_OPTIMISTIC_ATTEMPTS = 3;
const MAX_SESSION_REVISION_ATTEMPTS = 8;
const MAX_PRICING_SNAPSHOT_ATTEMPTS = 3;
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/renderer/desktop-transcript-range-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import { decodeStoredMessage, type StoredMessage } from '@maka/core/session';
import { markPersisted } from '@maka/core/persisted-value';
import type {
DesktopTranscriptBatchPayload,
DesktopTranscriptFragment,
Expand Down Expand Up @@ -308,7 +309,7 @@ export class DesktopTranscriptRangeStore {
const encoded = new TextDecoder('utf-8', { fatal: true }).decode(pending.bytes);
const message = projectDesktopStoredMessage(
{ hostId: this.#hostId },
decodeStoredMessage(JSON.parse(encoded) as unknown),
decodeStoredMessage(markPersisted<StoredMessage>(JSON.parse(encoded))),
);
const projected = JSON.stringify(message);
this.#pending.delete(key);
Expand Down
9 changes: 8 additions & 1 deletion packages/cli/src/runtime-host-session-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
* under the License.
*/

import { decodeStoredMessage, type StoredMessage } from '@maka/core/session';
import {
decodeStoredMessage as decodePersistedStoredMessage,
type StoredMessage,
} from '@maka/core/session';
import { markPersisted } from '@maka/core/persisted-value';
import { type SessionEvent } from '@maka/core/events';
import {
createRuntimeHostSessionProjectionSeed,
Expand All @@ -32,6 +36,7 @@ import {
type RuntimeHostConnection,
type RuntimeHostSessionSubscription,
} from '@maka/runtime-host/client';

import {
InteractionAnsweredSnapshot,
InteractionPendingSnapshot,
Expand All @@ -42,6 +47,8 @@ import {
} from '@maka/runtime-host/protocol';
import type { MakaPreparedSessionTurn } from './session-driver.js';

const decodeStoredMessage = (value: unknown): StoredMessage =>
decodePersistedStoredMessage(markPersisted<StoredMessage>(value));
const MAX_PENDING_FRAMES = 512;
const MAX_PENDING_EVENTS_PER_TURN = 1_024;
const LAG_REARM_PENDING_EVENTS = MAX_PENDING_EVENTS_PER_TURN / 2;
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/runtime-host-session-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@ import { randomUUID } from 'node:crypto';
import type { CreateSessionInput } from '@maka/core/runtime-inputs';
import { DEFAULT_SESSION_NAME } from '@maka/core/session-name';
import {
decodeStoredMessage,
decodeStoredMessage as decodePersistedStoredMessage,
userFacingText,
type SessionSummary,
type StoredMessage,
} from '@maka/core/session';
import { markPersisted } from '@maka/core/persisted-value';
import {
type ActiveInteractionRequestEvent,
type QueueEnqueueOutcome,
type SessionEvent,
type ShellRunUpdate,
} from '@maka/core/events';

import type { OrchestrationMode } from '@maka/core/orchestration';
import type { PermissionMode } from '@maka/core/permission';

Expand Down Expand Up @@ -89,6 +91,8 @@ import {
inspectGitCwdChanges,
resolveMoveCwd,
} from './session-driver-policy.js';
const decodeStoredMessage = (value: unknown): StoredMessage =>
decodePersistedStoredMessage(markPersisted<StoredMessage>(value));
const MAX_CATALOG_ATTEMPTS = 3;

/** Optimistic-control retries for goal pause/resume/clear (mirrors the desktop client). */
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"./model-metadata": "./dist/model-metadata.js",
"./model-web-search": "./dist/model-web-search.js",
"./model-thinking": "./dist/model-thinking.js",
"./persisted-value": "./dist/persisted-value.js",
"./chat-model-choice": "./dist/chat-model-choice.js",
"./connection-readiness": "./dist/connection-readiness.js",
"./provider-auth": "./dist/provider-auth.js",
Expand Down
39 changes: 34 additions & 5 deletions packages/core/src/__tests__/agent-run-authority.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { decodeAgentRunHeader, type AgentRunHeader } from '../agent-run.js';
import {
decodeAgentRunHeader,
decodePersistedAgentRunHeader,
type AgentRunHeader,
} from '../agent-run.js';
import { markPersisted } from '../persisted-value.js';

test('rejects a Run header with multiple hosted root authorities', () => {
assert.throws(
Expand All @@ -34,14 +39,38 @@ test('rejects a Run header with multiple hosted root authorities', () => {
});

test('decodes a released Automation Run as read-only legacy provenance', () => {
const decoded = decodeAgentRunHeader({
...runHeader(),
automationId: 'automation-1',
});
const decoded = decodePersistedAgentRunHeader(
markPersisted<AgentRunHeader>({
...runHeader(),
automationId: 'automation-1',
}),
);
assert.equal(decoded.legacyAutomationId, 'automation-1');
assert.equal(Object.hasOwn(decoded, 'automationId'), false);
});

test('folds all retired AgentRun values only at the persistence boundary', () => {
const persisted = {
...runHeader(),
status: 'waiting_permission',
permissionMode: 'execute',
};

const decoded = decodePersistedAgentRunHeader(markPersisted<AgentRunHeader>(persisted));
assert.equal(decoded.status, 'waiting_for_user');
assert.equal(decoded.permissionMode, 'ask');

assert.throws(() => decodeAgentRunHeader(persisted), /Invalid AgentRun header schema/);
assert.throws(
() => decodeAgentRunHeader({ ...runHeader(), automationId: 'automation-1' }),
/Invalid AgentRun header schema/,
);
assert.throws(
() => decodeAgentRunHeader({ ...runHeader(), permissionMode: 'execute' }),
/Invalid AgentRun header schema/,
);
});

function runHeader(): AgentRunHeader {
return {
runId: 'run-1',
Expand Down
70 changes: 70 additions & 0 deletions packages/core/src/__tests__/persisted-value-contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 assert from 'node:assert/strict';
import { test } from 'node:test';
import { markPersisted, type PersistedValue } from '../persisted-value.js';

interface ExampleRecord {
readonly id: string;
}

interface WiderRecord {
readonly id: string;
}

interface NarrowerRecord extends WiderRecord {
readonly detail: string;
}

test('persisted values cross into domain types only through an explicit decoder', () => {
const persisted = markPersisted<ExampleRecord>({ id: 'record-1' });
const decode = (value: PersistedValue<ExampleRecord>): ExampleRecord =>
value as unknown as ExampleRecord;

const assignWithoutDecode = () => {
// @ts-expect-error A persisted value is not a decoded domain record.
const current: ExampleRecord = persisted;
return current;
};
const passUnknownWithoutMarking = (value: unknown) => {
// @ts-expect-error Unknown input must be marked at a persistence read seam first.
return decode(value);
};
const passCurrentWithoutMarking = (value: ExampleRecord) => {
// @ts-expect-error Current domain values are not persisted decoder inputs.
return decode(value);
};

assert.deepEqual(decode(persisted), { id: 'record-1' });
assert.equal(typeof assignWithoutDecode, 'function');
assert.equal(typeof passUnknownWithoutMarking, 'function');
assert.equal(typeof passCurrentWithoutMarking, 'function');
});

test('persisted values are invariant in their domain type', () => {
const narrower = markPersisted<NarrowerRecord>({ id: 'record-1', detail: 'detail' });
const rejectWidening = () => {
// @ts-expect-error PersistedValue<T> must not widen across related record types.
const wider: PersistedValue<WiderRecord> = narrower;
return wider;
};

assert.equal(typeof rejectWidening, 'function');
});
10 changes: 5 additions & 5 deletions packages/core/src/__tests__/runtime-event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
type RuntimeEvent,
type RuntimeEventActions,
} from '../runtime-event.js';
import { decodeStoredMessage } from '../session.js';
import { decodeCanonicalMessage } from '../session.js';
import { decodeTurnOrigin } from '../turn-origin.js';

/** Minimal valid RuntimeEvent; callers spread overrides on top. */
Expand Down Expand Up @@ -68,7 +68,7 @@ test('Stored assistant reasoning parts survive recovery decoding', () => {
},
},
];
const stored = decodeStoredMessage({
const stored = decodeCanonicalMessage({
type: 'assistant',
id: 'message-1',
turnId: 'turn-1',
Expand All @@ -84,7 +84,7 @@ test('Stored assistant reasoning parts survive recovery decoding', () => {
});

test('decodes released Automation origins as read-only legacy provenance', () => {
const message = decodeStoredMessage({
const message = decodeCanonicalMessage({
type: 'user',
id: 'message-1',
turnId: 'turn-1',
Expand Down Expand Up @@ -419,7 +419,7 @@ describe('RuntimeEvent content variants', () => {
event.content && 'quotes' in event.content ? event.content.quotes?.[0] : undefined,
quotes[0],
);
const stored = decodeStoredMessage({
const stored = decodeCanonicalMessage({
type: 'user',
id: 'message-1',
turnId: 'turn-1',
Expand All @@ -443,7 +443,7 @@ describe('RuntimeEvent content variants', () => {
assert.notEqual(stored.quotes?.[0], quotes[0]);
assert.throws(
() =>
decodeStoredMessage({
decodeCanonicalMessage({
type: 'user',
id: 'message-1',
turnId: 'turn-1',
Expand Down
36 changes: 33 additions & 3 deletions packages/core/src/__tests__/scheduled-task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { markPersisted } from '../persisted-value.js';
import {
computeNextFireAt,
decodePersistedScheduledTask,
Expand Down Expand Up @@ -263,19 +264,48 @@ describe('decodePersistedScheduledTask', () => {
const stored = JSON.parse(
JSON.stringify(base).replace('"permissionMode":"ask"', '"permissionMode":"execute"'),
) as ScheduledTask;
const decoded = decodePersistedScheduledTask(stored);
const decoded = decodePersistedScheduledTask(markPersisted<ScheduledTask>(stored));
assert.equal(
decoded.effect.kind === 'agent_run' ? decoded.effect.execution.permissionMode : undefined,
'ask',
);
});

it('returns the same task when nothing needs folding', () => {
assert.equal(decodePersistedScheduledTask(base), base);
assert.equal(decodePersistedScheduledTask(markPersisted<ScheduledTask>(base)), base);
});

it('leaves effects without an execution template alone', () => {
const notify: ScheduledTask = { ...base, effect: { kind: 'notify', channel: 'local' } };
assert.equal(decodePersistedScheduledTask(notify), notify);
assert.equal(decodePersistedScheduledTask(markPersisted<ScheduledTask>(notify)), notify);
});

it('rejects unknown permission modes in persisted execution templates', () => {
assert.throws(
() =>
decodePersistedScheduledTask(
markPersisted<ScheduledTask>({
...base,
effect: {
...base.effect,
execution: {
...(base.effect.kind === 'agent_run' ? base.effect.execution : {}),
permissionMode: 'future-mode',
},
},
}),
),
/Invalid persisted ScheduledTask permission mode/,
);
});

it('rejects unknown effect kinds in persisted records', () => {
assert.throws(
() =>
decodePersistedScheduledTask(
markPersisted<ScheduledTask>({ ...base, effect: { kind: 'future-effect' } }),
),
/Invalid persisted ScheduledTask effect/,
);
});
});
Loading