mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-26 08:58:04 +00:00
* test(plugin-sdk): remove duplicated s0029 owner coverage Keep nullable object/literal branch proof in provider-tools.nullable.test.ts and native Codex schema proof in schema-normalization-runtime-contract.test.ts. Keep memory export identity, journal ordering, byte verification, and interrupted-export cases in memory-host-event-export.test.ts. Keep recovery and private projection cases in session-store-runtime.recovery.test.ts and session-store-runtime.writer-claim.test.ts. * test(plugin-sdk): simplify remaining s0029 fixture layers Reuse exact-path memory export race setup, simplify compiler process census and tuple resources, and consolidate cancellation assertions without dropping authority checks. Remove mocked activated-runner inventory replay covered by qa-runner-runtime.integration.test.ts: loads an activated qa runner from a linked plugin path without a bundled install fallback. Preserve valid module-flow metadata in the private-source discovery case. * test(process): deslop s0030 tests Remove duplicated input admission cases from exec.test.ts: equivalent cases remain in exec-input-admission.test.ts. Remove copied queue cancellation cases covered by command-queue.cancellation.test.ts and copied Node PTY cases covered by terminal-pty-node.test.ts. Consolidate local lifecycle fixtures and exact target assertions; remove redundant diagnostic label variants while retaining branch and privacy proof. Preserve distinct authority, process identity, cleanup ordering, Windows launch and encoding cases. CI-only validation: plain oxlint, oxfmt and git diff --check passed. No tests or coverage were run. Shard lines 5944 to 5255 (11.6 percent); stopped short of 25 percent to retain distinct boundary behavior. * test(process): consolidate s0030 routing and queue fixtures Fold Bun backend routing into the existing terminal-pty three-backend test while preserving authority callbacks, backend selection, handle identity, environment normalization and input immutability. Remove the duplicate routing fixture from terminal-pty-bun. Share parked-task completion and queue-capacity assertions. Exercise active completion and new-enqueue rejection in one drain flow; stop-reason classification remains covered by gateway-work-admission tests. CI-only: formatting, plain oxlint and diff-check pass; no tests or coverage run. Additional reduction is 43 lines, original shard total 5944 to 5212. Remaining distinct authority and lifecycle proof prevents the requested 25 percent cut without weakening coverage. * test(process): trim s0030 implementation-only assertions Remove queue pointer projections, redundant registration identity and process-read accounting while keeping FIFO, generation, work-count and signal-ownership outcomes. Merge deferred Git-owner proof into the async owner case; preserve both Git owners and private-label rejection. Table raw-group extinction outcomes, share fixture settlement tracking, and remove repeated PTY normalization covered by retained native routing and real terminal I/O. Reduce Windows supervisor status replays through the shared uncertain-outcome branch, retaining Job error-source and public execution-result checks. Preserve idle replacement identity, session-target retention, idempotent root release and all distinct authority/lifecycle assertions. CI-only: plain oxlint, formatting and diff checks passed; no tests or coverage run. No production changes. * test(outbound): deslop s0032 tests Remove duplicate namespace and preparation cases from delivery-queue.storage; their owner proof remains in delivery-queue-namespaces.test.ts and delivery-queue-preparation.test.ts. Remove the duplicate mirror block from message-action-send.core; message-action-send.mirror-order.test.ts retains its persistence, identity, suppression and writer-ordering assertions. Consolidate Gateway request fixtures and rejection rows without dropping receipt or preflight boundaries; exercise forwarded delivery fields together. Preserve distinct custody, binding, completion and sandbox cases. CI-only validation: plain oxlint, oxfmt and diff check passed; runtime tests and coverage deferred to hosted CI. * test(outbound): consolidate s0032 send fixtures Move Gateway reconciliation cases into message-action-send.test.ts under the same infra project and reuse its plugin fixture. Keep both bounded and signal-bound timeout branches in one table. Combine blank-location and presentation routing proof, and direct reply/thread forwarding inputs. Share existing follow-up builders and barrier setup. Retain separate cross-channel and same-channel threadless route rows while sharing their persistence setup. Preserve custody, failed-write recovery, replacement-owner and dispatch-order assertions. Static lint, formatting and diff checks passed; no test or coverage runs per CI-only instruction. * test(agents): deslop s0033 tests Consolidate repeated delivery, runtime and thinking fixtures while preserving authority, privacy and lifecycle assertions. Remove duplicate generated Code Mode parser sweeps; the preserves and rejects tables retain their module-loader and division-context coverage. No production changes. CI-only shard: plain oxlint, oxfmt and diff-check passed. Runtime and coverage checks deferred to hosted CI. LOC 5926 to 5508; below the 25 percent target because remaining cases protect distinct boundaries. * test(agents): consolidate s0033 duplicate layers Keep watchdog and idle-GC proofs in code-mode-node.watchdog.test.ts; remove the four repeated cases and their pool fixtures from code-mode-node.test.ts. Reduce old tokenizer combinations to the current AST branches. Keep automatic thread suppression in the source-reply-suppression suite, self-send route capture in sessions-send-tool.admission, and successful steering provenance in sessions-steering. Preserve rejected text delivery followed by silence, compaction fallback order, and unconfirmed commitment no-replay checks. Consolidate persistence setters and MCP proto partition coverage; minimize table fixtures and remove duplicated SQLite reopen, fixture schema and internal inspection assertions. Plain oxlint, oxfmt and diff-check pass. No test runs under CI-only mode. * test(agents): factor s0033 fixtures and branch tables Share acquisition barriers, admission checks, payload capture and resume defaults. Consolidate output-error and thinking transport cases; keep one malformed-source case per relevant parser path. Remove redundant fixture-shaped projection and runtime-cache assertions while retaining observable outcomes. Preserve real SSE proof, transcript commitment, authority, privacy, cancellation and resource lifecycle assertions. CI-only: plain oxlint, oxfmt and diff-check passed; no tests run. Shard LOC is 5926 to 4885; the 25 percent target remains unmet. * test(config): deslop s0031 tests Consolidate pending-history staging fixtures, reuse the shared host-SQL observer, and reuse existing message and committed-mutation builders without deleting cases. Retain authority, SQLite durability, archive-race and lifecycle ordering assertions; further structural deletion was not justified under CI-only proof. * test(config): consolidate s0031 test layers Remove duplicate kernel, filesystem-probe, query-count and diagnostic-schema layers while retaining public history, custody, lifecycle and archive-race proof. Consolidate cold batch stats into the archival race and metadata preservation into the native/worker refresh scenario. Remaining proof: session-input-concurrency preserves input persistence and authority across metadata and adoptRun lifecycle changes; transcript-tree owns reachable-suffix and parentless navigation, with cross-store fork rows retaining integration proof; session-accessor.pending-inputs-consumed rejects conflicting completion identities; sqlite-transaction.diagnostics owns diagnostic field schemas. Keep current-membership staging admission, terminal settlement, no-leaked-lease and redaction assertions. * test(config): reduce redundant s0031 owner layers Consolidate mirror batching and branch facts, cached entry replacement, and memory/durable federation fixtures. Keep admission-refusal diagnostics and unchanged data proof. Remaining owner proof: agent-deletion-journal.startup covers rollback and completed tombstone publication; session-actor-memory, memory-storage and memory-history cover close, release and rotation; sqlite-reclamation-reuse covers idle retirement and shared worker scans. Pending history reset custody remains in session-accessor.pending-inputs. CI-only: plain oxlint, oxfmt and diff-check passed; no tests or coverage run. Original shard 5717 lines to 4806 (15.9% reduction), below the requested 25% because remaining authority, durability and lifecycle paths are distinct. * test(agents): deslop s0035 tests Consolidate prompt checkpoint fixtures, retry event assertions, transcript rewrite helpers, and CLI dispatch setup. Preserve distinct authority, recovery, privacy, and lifecycle contracts. CLI subscription auth, cleanup ownership, and binding stripping remain in maps the embedded run onto a one-shot restricted CLI run. Phase and tool-result assertions remain in mirrors CLI events and detaches the tool-result bridge after settlement. The raw route variant shares the retained undefined route branch; resolved route coverage remains. CI-only: plain oxlint, oxfmt, and diff checks pass. No tests, coverage, or builds run. Shard LOC 6178 to 5925; below the 25 percent target because remaining cases guard distinct protected behaviors. * test(agents): consolidate s0035 duplicate proof layers Remove duplicated plugin cursor and rewrite notification cases from transcript-rewrite; identical keepers live in transcript-rewrite-plugin-reads.test.ts and transcript-rewrite-notifications.test.ts. Metadata label remapping remains in transcript-rewrite-metadata.test.ts. Keep question-answer ownership and generic waiter replacement proof in runs.steering.test.ts. Keep prefix refresh proof in run/attempt.llm-boundary.cache-stability.test.ts. Keep Bedrock merge and Anthropic append-only replay proof in the sanitizer owner suites. Consolidate CLI eligibility through async dispatch, retry branch matrices, prompt checkpoint fixtures and SQLite observation. Preserve cross-owner finalization callbacks, authority, recovery and durability assertions. CI-only: plain oxlint, oxfmt and diff checks pass. No tests or builds run. Shard LOC 6178 to 5180 overall, 5925 to 5180 this pass; the 25 percent target remains unmet. * test(sessions): consolidate s0034 lifecycle fixtures Port s0034 commits 6b787c523fa99506edbe643672876b3f57dffa6e, 57a45bd3eb6eafc89079374563be347e674405ce, and d0e9e5389d3fa4391bf0c77e837d3b150501ca71 as one lane commit. Keep the newer lane index-family harness unchanged, including its client return, default row arguments, and own-route guard. All nine test files were unchanged between the shard base and lane HEAD; their ported content matches the final shard. CI-only validation: oxlint and oxfmt --check pass for all nine touched files; git diff --check and staged diff check pass. Independent autoreview is scoped-clean at P0. No runtime tests or coverage were run; affected tests are deferred to landing CI. * test(gateway): deslop s0036 tests Consolidate settled startup catalog assertions into serves published startup with only auth revision reads. The reuses the prepared generation for equivalent config and auth revisions test retains unchanged-generation coverage. Share direct-followup admission/config/event fixtures and transcript RPC assertions while preserving authority, privacy, ordering, and cleanup checks. CI-only campaign: formatting, plain oxlint and diff-check passed; no test or coverage execution. * test(gateway): reduce s0036 duplicate boundary proof Drop CLI pagination replays covered by server.chat.cli-history.test.ts (full local prefix and external-only rows; every older import duplicates a local row), cli-session-history.paging.test.ts, and chat-history-request-budget.test.ts. Preserve anchored metadata, hidden anchors, recovery, and reset assertions in retained cases. Remove transcript library projection and size-limit replays covered by library.test.ts and shared profile failure coverage owned by server-methods.profile-authorization.test.ts. Consolidate sharing write assertions into the actor-evidence flow, preserving transcript neutrality and scoped events. Default-session authorization remains covered through chat-send-profile-binding.test.ts. Remove category forwarding and bootstrap projection replays owned by clawhub-plugin-catalog.test.ts and bootstrap-files.personal.test.ts. Retain authority, privacy, persistence, and lifecycle interleavings. CI-only: plain oxlint, formatting, and diff-check pass; no tests or coverage run. * test(gateway): consolidate s0036 fixtures and assertions Remove redundant private getter call ledgers and repeated ready reads while retaining catalog work-count and startup output checks. Reuse the question answer fixture and recovery parameters without changing recovery timing. Consolidate plugin inspection and pagination fixtures, keeping nonblocking remote presentation and package-alias collision controls. Inject owner-source revocation at the existing awaited profile hook. Reuse suggestion role-policy and membership setup while retaining every role, privacy, and lifecycle assertion. CI-only campaign: plain oxlint, formatting, and diff-check passed. No tests, builds, or coverage executed. The original 25 percent line target remains unmet; retained refusal/replay and post-cancellation media scenarios are not duplicated by their sibling tests. * test(tooling): deslop s0038 tests Share cancellable registry response fixtures and reuse the existing scoped config reader. Preserve all cases and assertions; the remaining shard exercises distinct package, release, routing, authority, and recovery contracts. * test(tooling): consolidate s0038 owner-boundary proofs Remove duplicated CLI Gateway ownership coverage retained in test/vitest-cli-ownership.test.ts. Keep withdrawal replay proof in test/scripts/verify-stable-main-closeout.test.ts and package metadata/artwork proof in test/plugin-npm-package-manifest.test.ts. Retain executable msteams loading instead of source-shape checks. Reduce QA cross-products while retaining each authority boundary, and consolidate fixtures and observable-state assertions. Static lint, formatting and diff-check only per CI-only mode; no runtime or coverage claim. * test(tooling): simplify s0038 native probe assertions Assert discovery, partitioning and scheduling contracts inside the native child fixtures instead of serializing reports and repeating their schemas and assertions in parent tests. Keep all error, ownership, lifecycle and worker-policy checks. Consolidate registry rejection and loopback server fixtures. Count support-file growth in the reduction; validation is formatting, plain oxlint and diff-check only per CI-only mode. * test(system-agent): deslop s0037 tests Reuse config/audit fixtures and consolidate post-write warning setup without removing distinct behaviors. Preserve authority, credential, lifecycle, recovery, and redaction assertions. CI-only validation: oxfmt, plain oxlint, and diff-check; test execution remains for hosted CI. The safe reduction is 107 lines (1.9%), below the 25% target. * test(system-agent): consolidate s0037 behavior coverage Consolidate route and repair tables, cache-delivery lifecycle checks, and protected-key revalidation. Remove obsolete rescue mocks, redundant credential variants, copied fixtures, and incidental wiring assertions. Coverage handoffs: inference-route admits an uncreated owner only for an explicit primary preflight; setup-inference-turn runs a bounded read-only probe with a detached transcript; operations.setup owns default-model persistence; operations.roles owns persisted named-agent role/purpose; the retained configured embedded planner owns local timeout dispatch. Keep direct assertions for credential isolation, auth revocation, approval flow, rollback, and lifecycle ordering. CI-only: formatting, plain oxlint, and diff-check passed. No tests, coverage, or builds run. Total shard 5761 -> 5289 lines (-8.2%); the 25% target remains unmet rather than deleting distinct protected behaviors. * test(system-agent): remove redundant s0037 test layers Consolidate tool-free planner dispatch, setup rejection branches, greeting read failures and rescue refusal policies. Remove the rescue model executor replay and its mutable config emulator, copied hosted channel-policy setup, and repeated lower-level tool/skills checks. Remaining owner proof: src/agents/embedded-agent-runner/run/attempt-setup.test.ts prepares readable skills with execution denied; src/agents/tools/system-agent-tool.test.ts stages exact creation proposals and enforces operator approval; chat-turn-router.approval.test.ts covers creation provenance; src/flows/channel-setup.test.ts covers stale external account policy installation; src/agents/agent-scope.test.ts owns explicit model writes. Keep conversation dispatch gates, credential isolation, receipt ownership, rollback and lifecycle ordering in this shard. CI-only validation: plain oxlint, oxfmt and diff-check pass. No test/build/coverage run. Original shard 5761 -> 4738 lines (-17.8%); 551 additional lines removed. The 25% target remains unmet to preserve distinct protected behavior. * test(commands): deslop s0039 tests * test(commands): consolidate s0039 test layers Remove the embedded released Windows updater matrix: doctor-update-schema-guard.legacy-windows.test.ts already owns refuses the released Windows ledger incompatibility before writes ($name) and preserves existing Doctor admission for $name. Keep archive coalescing receipt proof in doctor-session-sqlite-recovery-inventory.test.ts: recovers duplicate retirement interrupted during %s. Retain discovery search and differing-archive preservation checks. Consolidate fixture defaults, same-path readiness cases, retirement setup, and Azure reasoning setup while preserving distinct safety boundaries. Static checks only under campaign CI-only mode. * test(commands): trim s0039 assertion scaffolding Keep local fallback credential variants in one table; preserve explicit and paired-device auth assertions. Remove preflight result-shape and verification-call-count assertions while retaining real backup restore, WAL rows, owner refusal, and unchanged-byte proofs in the same tests. Preserve the original error object directly instead of wrapping one migration in an allSettled array. CI-only validation. * test(gateway): deslop s0040 tests Remove duplicated wizard authority probes and login disconnect cases covered by wizard.authority.test.ts. Keep rejoin, concurrent eviction and requester-revocation lifecycle proof in system-agent.session-lifecycle.test.ts; sensitive metadata remains covered by the real-engine masking case in system-agent.test.ts. Reuse native model configuration helpers and consolidate session custody, row-facts and operator-preparation fixtures without removing distinct authority, persistence or ordering assertions. CI-only validation: plain oxlint, oxfmt and git diff --check; runtime tests deferred to hosted CI. * test(gateway): remove redundant s0040 test layers Keep native Gateway startup, config reload, account renewal and provider-scope isolation; remove runtime replays covered by prepared-model-runtime.native-demand.test.ts, prepared-model-runtime.native-picker-failures.test.ts and models-list.discovery-lifecycle.integration.test.ts. Keep startup and describe policy boundaries; history, exact-message, recent and roster redaction remain covered by chat-history-model-policy.test.ts. Remove duplicate progress diagnostics covered by session-catalog-provider-diagnostics.test.ts and session-catalog-phase-diagnostics.test.ts. Runtime-install projection remains covered by placement-projector.test.ts; disposable trust by environments.pool.test.ts. Shrink fixtures, copied mock inventories and self-oracle setup while retaining independent expiry, authorization and ordering assertions. CI-only: oxfmt, plain oxlint and diff check pass; no tests run. Shard reduction remains below the 25 percent target to preserve unique contracts. * test(outbound): retain reconciliation coverage after conflict resolution * test(process): guard controlled Bun terminal callbacks * test: repair d503 fixture typing and retain lifecycle proof * test: retain d503 boundary fixtures and repair lint contracts * test: drop d503 reductions with failing boundary fixtures * test(plugin-sdk): retain hosted media point-read round trip
448 lines
19 KiB
TypeScript
448 lines
19 KiB
TypeScript
import { StatementSync } from "node:sqlite";
|
|
import { expectDefined } from "@openclaw/normalization-core";
|
|
import { isRecord } from "@openclaw/normalization-core/record-coerce";
|
|
import { createPluginRuntimeMock } from "openclaw/plugin-sdk/plugin-test-runtime";
|
|
import { afterEach, expect, it, vi } from "vitest";
|
|
import { createCodexSessionInitializationFixtureForTest } from "../extensions/codex/test-api.js";
|
|
import {
|
|
loadSessionEntry,
|
|
loadTranscriptEvents,
|
|
rollbackAgentHarnessSessionEntryLifecycle,
|
|
} from "../src/config/sessions/session-accessor.js";
|
|
import { writeSessionEntry } from "../src/config/sessions/session-accessor.sqlite-entry-store.js";
|
|
import { sessionRewindHandlers } from "../src/gateway/server-methods/sessions-rewind.js";
|
|
import { callGatewayHandler } from "../src/gateway/server-methods/skills.test-helpers.js";
|
|
import * as sqliteAdmission from "../src/infra/sqlite-worker-operation-admission.js";
|
|
import {
|
|
createPluginStateKeyedStoreV2,
|
|
createPluginStateSyncKeyedStore,
|
|
type OpenAsyncKeyedStoreOptions,
|
|
type OpenKeyedStoreOptions,
|
|
type PluginStateActionAuthority,
|
|
} from "../src/plugin-state/plugin-state-store.js";
|
|
import { createEmptyPluginRegistry } from "../src/plugins/registry-empty.js";
|
|
import { markPluginRegistryActive } from "../src/plugins/registry-lifecycle.js";
|
|
import { withPluginRuntimeRegistryScope } from "../src/plugins/runtime/gateway-request-scope.js";
|
|
import { createRuntimeAgent } from "../src/plugins/runtime/runtime-agent.js";
|
|
import { createPluginRecord } from "../src/plugins/status.test-helpers.js";
|
|
import type { SessionInitialization } from "../src/sessions/session-initialization.js";
|
|
import { sessionChanges } from "../src/sessions/session-row-changes.js";
|
|
import * as upstreamLinks from "../src/sessions/session-upstream-links.js";
|
|
import { readSessionUpstreamLinkInDatabase } from "../src/sessions/session-upstream-links.kernel.js";
|
|
import {
|
|
openOpenClawAgentDatabase,
|
|
runOpenClawAgentWriteTransaction,
|
|
} from "../src/state/openclaw-agent-db.js";
|
|
import { openOpenClawStateDatabase } from "../src/state/openclaw-state-db.js";
|
|
import { observeMainThreadReads } from "../src/test-utils/main-thread-sql-spies.test-support.js";
|
|
import { withOpenClawTestState } from "../src/test-utils/openclaw-test-state.js";
|
|
|
|
function writeLink(input: Parameters<typeof upstreamLinks.upsertSessionUpstreamLink>[0]) {
|
|
expect(upstreamLinks.upsertSessionUpstreamLink(input)).toBe(true);
|
|
}
|
|
|
|
function readLink(sessionKey: string, agentId = "main") {
|
|
return readSessionUpstreamLinkInDatabase(openOpenClawStateDatabase().db, sessionKey, agentId);
|
|
}
|
|
|
|
afterEach(() => vi.restoreAllMocks());
|
|
|
|
it.each([
|
|
...[
|
|
"callback",
|
|
"import",
|
|
"before binding",
|
|
"after binding",
|
|
"final readiness",
|
|
"readiness publication",
|
|
"lost response",
|
|
"successor binding",
|
|
"successor link",
|
|
"existing link",
|
|
"source successor",
|
|
"rollback commit",
|
|
"native cleanup",
|
|
].map((failure) => ({ flow: "fork", failure })),
|
|
...["callback", "before binding", "after binding", "final readiness", "rollback commit"].map(
|
|
(failure) => ({ flow: "adoption", failure }),
|
|
),
|
|
])(
|
|
"preserves exactly the committed owner after $flow $failure failure",
|
|
async ({ flow, failure }) => {
|
|
await withOpenClawTestState({ label: "codex-initialization-owner" }, async (state) => {
|
|
const agent = createRuntimeAgent();
|
|
const runtime = createPluginRuntimeMock({
|
|
agent,
|
|
state: {
|
|
openKeyedStoreV2: <T>(
|
|
options: OpenAsyncKeyedStoreOptions,
|
|
authority: PluginStateActionAuthority = { assertCurrent() {} },
|
|
) => createPluginStateKeyedStoreV2<T>("codex", { ...options, env: state.env }, authority),
|
|
openSyncKeyedStore: <T>(options: OpenKeyedStoreOptions) =>
|
|
createPluginStateSyncKeyedStore<T>("codex", { ...options, env: state.env }),
|
|
},
|
|
});
|
|
const fixture = await createCodexSessionInitializationFixtureForTest({
|
|
runtime,
|
|
workspaceDir: state.workspaceDir,
|
|
});
|
|
const { params, sourceThread, forkedThread, native, bindingStore, harness } = fixture;
|
|
const sourceBinding = bindingStore.read(fixture.sourceIdentity);
|
|
const sourceEntry = loadSessionEntry(params.source);
|
|
let expectedSourceEntry = sourceEntry;
|
|
const replaceSource = () => {
|
|
runOpenClawAgentWriteTransaction(
|
|
(database) =>
|
|
writeSessionEntry(database, params.source.sessionKey, {
|
|
...expectDefined(sourceEntry, "source"),
|
|
sessionId: "source-successor",
|
|
lifecycleRevision: "successor-generation",
|
|
}),
|
|
{ agentId: "main" },
|
|
);
|
|
expectedSourceEntry = loadSessionEntry({ ...params.source, readConsistency: "latest" });
|
|
};
|
|
const sourceHistory = await loadTranscriptEvents(params.source);
|
|
let linkOperation: "write" | "cleanup" | undefined;
|
|
let linkGrantReads = 0;
|
|
let rollbackCommitRefused = false;
|
|
let rejectReadinessCommit = false;
|
|
let readinessCommitRefused = false;
|
|
let readinessPublicationRefused = false;
|
|
let readyAtPublication: ReturnType<typeof loadSessionEntry>;
|
|
let stopReadinessPublication: (() => void) | undefined;
|
|
const reads = observeMainThreadReads();
|
|
const upsertLink = upstreamLinks.upsertSessionUpstreamLinkWithCurrentSource;
|
|
const deleteLink = upstreamLinks.deleteSessionUpstreamLinkAsync;
|
|
const trackLink =
|
|
<Args extends unknown[], Result>(
|
|
operation: "write" | "cleanup",
|
|
run: (...args: Args) => Promise<Result>,
|
|
) =>
|
|
async (...args: Args) => {
|
|
linkOperation = operation;
|
|
try {
|
|
return await run(...args);
|
|
} finally {
|
|
linkOperation = undefined;
|
|
}
|
|
};
|
|
vi.spyOn(upstreamLinks, "upsertSessionUpstreamLinkWithCurrentSource").mockImplementation(
|
|
trackLink("write", upsertLink),
|
|
);
|
|
vi.spyOn(upstreamLinks, "deleteSessionUpstreamLinkAsync").mockImplementation(
|
|
trackLink("cleanup", deleteLink),
|
|
);
|
|
const createAdmission = sqliteAdmission.createSqliteWorkerOperationAdmission;
|
|
vi.spyOn(sqliteAdmission, "createSqliteWorkerOperationAdmission").mockImplementation(
|
|
(admit, attachment) => {
|
|
const operation = linkOperation;
|
|
return createAdmission((request, grant) => {
|
|
const publication =
|
|
isRecord(request.facts) && isRecord(request.facts.publication)
|
|
? request.facts.publication.kind
|
|
: undefined;
|
|
if (
|
|
rejectReadinessCommit &&
|
|
request.stage === "commit" &&
|
|
publication === "session-entry-patch-committed"
|
|
) {
|
|
rejectReadinessCommit = false;
|
|
readinessCommitRefused = true;
|
|
throw new Error("injected readiness failure");
|
|
}
|
|
if (
|
|
failure === "rollback commit" &&
|
|
request.stage === "commit" &&
|
|
publication === "session-native-binding"
|
|
) {
|
|
rollbackCommitRefused = true;
|
|
throw new Error("injected rollback failure");
|
|
}
|
|
const offsets = reads.calls.map((call) => call.mock.contexts.length);
|
|
try {
|
|
admit(request, grant);
|
|
} finally {
|
|
if (operation && (request.stage === "transaction" || request.stage === "commit")) {
|
|
for (const [index, call] of reads.calls.entries()) {
|
|
linkGrantReads += call.mock.contexts
|
|
.slice(offsets[index])
|
|
.filter(
|
|
(statement) =>
|
|
statement instanceof StatementSync &&
|
|
statement.sourceSQL.includes("session_upstream_links"),
|
|
).length;
|
|
}
|
|
}
|
|
}
|
|
}, attachment);
|
|
},
|
|
);
|
|
const registry = createEmptyPluginRegistry();
|
|
registry.plugins.push(createPluginRecord({ id: "codex" }));
|
|
registry.agentHarnesses.push({ pluginId: "codex", source: "runtime", harness });
|
|
markPluginRegistryActive(registry);
|
|
const invokeFork = async () => {
|
|
writeLink({
|
|
sessionKey: params.source.sessionKey,
|
|
agentId: "main",
|
|
catalogId: params.upstream.catalogId,
|
|
hostId: params.upstream.hostId,
|
|
upstreamKind: params.upstream.kind,
|
|
threadId: sourceThread.id,
|
|
upstreamRef: params.upstream.ref,
|
|
marker: null,
|
|
});
|
|
const result = await callGatewayHandler(
|
|
sessionRewindHandlers,
|
|
"sessions.fork",
|
|
{ sessionKey: params.source.sessionKey, entryId: params.source.entryId },
|
|
{
|
|
context: {
|
|
getRuntimeConfig: () => ({}),
|
|
getSessionEventSubscriberConnIds: () => new Set(),
|
|
broadcastToConnIds: vi.fn(),
|
|
},
|
|
},
|
|
);
|
|
expect(result.ok).not.toBeNull();
|
|
const message = isRecord(result.error) ? result.error.message : undefined;
|
|
return {
|
|
status: result.ok ? "created" : "failed",
|
|
...(typeof message === "string" ? { message } : {}),
|
|
};
|
|
};
|
|
const deletion = vi.spyOn(harness, "withSessionDeletion");
|
|
const mutate = bindingStore.mutate.bind(bindingStore);
|
|
let childSessionId: string | undefined;
|
|
let retained: SessionInitialization | undefined;
|
|
const create = runtime.agent.session.createSessionEntry;
|
|
vi.spyOn(runtime.agent.session, "createSessionEntry").mockImplementation(async (input) => {
|
|
const created = await create({
|
|
...input,
|
|
afterCreate: async (context) => {
|
|
childSessionId = context.sessionId;
|
|
params.targetKey = context.key;
|
|
retained = context.initialization;
|
|
if (failure === "callback") {
|
|
throw new Error("injected callback failure");
|
|
}
|
|
if (failure === "existing link") {
|
|
writeLink({
|
|
sessionKey: context.key,
|
|
agentId: context.agentId,
|
|
catalogId: "codex",
|
|
hostId: "gateway:local",
|
|
upstreamKind: "codex-app-server",
|
|
threadId: "existing-successor",
|
|
upstreamRef: { threadId: "existing-successor" },
|
|
marker: null,
|
|
});
|
|
successorLink = readLink(context.key, context.agentId);
|
|
}
|
|
let patch: Awaited<ReturnType<NonNullable<typeof input.afterCreate>>>;
|
|
try {
|
|
patch = await input.afterCreate!(context);
|
|
} finally {
|
|
if (failure === "import") {
|
|
// The import fault must not alter schema admission during rollback.
|
|
openOpenClawAgentDatabase({ agentId: "main" }).db.exec(
|
|
"DROP TRIGGER reject_import",
|
|
);
|
|
}
|
|
}
|
|
if (!patch) {
|
|
throw new Error("Codex initializer did not return its final patch");
|
|
}
|
|
rejectReadinessCommit = failure === "final readiness";
|
|
return patch;
|
|
},
|
|
});
|
|
if (failure === "lost response") {
|
|
expect(created.entry.initializationPending).toBeUndefined();
|
|
throw new Error("injected response loss after readiness");
|
|
}
|
|
return created;
|
|
});
|
|
let successorBinding: Awaited<ReturnType<typeof bindingStore.read>>;
|
|
let successorLink: ReturnType<typeof readSessionUpstreamLinkInDatabase>;
|
|
if (failure === "native cleanup") {
|
|
native.archiveThread.mockRejectedValue(new Error("injected archive failure"));
|
|
native.control.retireConnection = vi.fn();
|
|
}
|
|
vi.spyOn(bindingStore, "mutate").mockImplementation(async (identity, mutation, guard) => {
|
|
if (mutation.kind !== "set") {
|
|
return await mutate(identity, mutation, guard);
|
|
}
|
|
if (identity.kind === "session") {
|
|
childSessionId = identity.sessionId;
|
|
}
|
|
if (failure === "before binding") {
|
|
throw new Error("injected binding write failure");
|
|
}
|
|
const attached = await mutate(identity, mutation, guard);
|
|
expect(attached).toBe(true);
|
|
if (failure === "source successor") {
|
|
replaceSource();
|
|
}
|
|
if (failure === "successor binding") {
|
|
await mutate(identity, {
|
|
kind: "patch",
|
|
threadId: forkedThread.id,
|
|
patch: { model: "successor-model" },
|
|
});
|
|
successorBinding = bindingStore.read(identity);
|
|
}
|
|
if (failure === "successor link") {
|
|
writeLink({
|
|
...expectDefined(readLink(params.targetKey), "created link"),
|
|
marker: { turnId: "successor-turn", userMessageCount: 2 },
|
|
threadId: "successor-thread",
|
|
});
|
|
successorLink = readLink(params.targetKey);
|
|
}
|
|
if (
|
|
[
|
|
"after binding",
|
|
"successor binding",
|
|
"successor link",
|
|
"rollback commit",
|
|
"native cleanup",
|
|
].includes(failure)
|
|
) {
|
|
throw new Error("injected post-write failure");
|
|
}
|
|
if (failure === "readiness publication") {
|
|
stopReadinessPublication = sessionChanges.subscribe((change) => {
|
|
if (
|
|
readinessPublicationRefused ||
|
|
!("sessionKey" in change) ||
|
|
change.sessionKey !== params.targetKey
|
|
) {
|
|
return;
|
|
}
|
|
const ready = loadSessionEntry({
|
|
agentId: "main",
|
|
sessionKey: params.targetKey,
|
|
readConsistency: "latest",
|
|
});
|
|
if (
|
|
!ready ||
|
|
ready.sessionId !== childSessionId ||
|
|
ready.initializationPending === true
|
|
) {
|
|
return;
|
|
}
|
|
readyAtPublication = ready;
|
|
readinessPublicationRefused = true;
|
|
throw new Error("injected readiness publication failure");
|
|
});
|
|
}
|
|
return attached;
|
|
});
|
|
|
|
if (failure === "import") {
|
|
// Allow the child header so the callback captures identity before imported messages fail.
|
|
openOpenClawAgentDatabase({ agentId: "main" }).db.exec(
|
|
"CREATE TRIGGER reject_import BEFORE INSERT ON transcript_events WHEN NEW.navigation_type = 'message' BEGIN SELECT RAISE(ABORT, 'injected import failure'); END",
|
|
);
|
|
}
|
|
const result = await withPluginRuntimeRegistryScope(
|
|
registry,
|
|
flow === "fork" ? invokeFork : fixture.adopt,
|
|
).finally(() => stopReadinessPublication?.());
|
|
|
|
if (failure === "rollback commit") {
|
|
expect(rollbackCommitRefused).toBe(true);
|
|
}
|
|
if (failure === "import") {
|
|
expect(result.message).toContain("injected import failure");
|
|
}
|
|
if (failure === "final readiness") {
|
|
expect(readinessCommitRefused).toBe(true);
|
|
expect(result.message).toContain("injected readiness failure");
|
|
}
|
|
if (failure === "readiness publication") {
|
|
expect(readinessPublicationRefused).toBe(true);
|
|
expect(readyAtPublication?.sessionId).toBe(childSessionId);
|
|
expect(readyAtPublication?.initializationPending).toBeUndefined();
|
|
}
|
|
expect(linkGrantReads).toBe(0);
|
|
expect(result.status).toBe(failure === "readiness publication" ? "created" : "failed");
|
|
const identity = {
|
|
kind: "session" as const,
|
|
agentId: "main",
|
|
sessionKey: params.targetKey,
|
|
sessionId: expectDefined(childSessionId, "created child identity"),
|
|
};
|
|
const child = loadSessionEntry(identity);
|
|
const binding = bindingStore.read(identity);
|
|
const link = readLink(params.targetKey);
|
|
if (failure === "lost response" || failure === "readiness publication") {
|
|
expect(child?.initializationPending).toBeUndefined();
|
|
expect(child?.sessionId).toBe(childSessionId);
|
|
expect(binding?.threadId).toBe(forkedThread.id);
|
|
expect(link?.threadId).toBe(forkedThread.id);
|
|
expect(native.archiveThread).not.toHaveBeenCalled();
|
|
expect(deletion).not.toHaveBeenCalled();
|
|
} else if (["successor binding", "rollback commit"].includes(failure)) {
|
|
expect(child?.initializationPending).toBe(true);
|
|
expect(binding).toEqual(
|
|
successorBinding ??
|
|
expect.objectContaining({
|
|
threadId: flow === "fork" ? forkedThread.id : sourceThread.id,
|
|
}),
|
|
);
|
|
expect(link?.threadId).toBe(flow === "fork" ? forkedThread.id : undefined);
|
|
expect(native.archiveThread).not.toHaveBeenCalled();
|
|
expect(result.message).toContain("guarded rollback did not complete");
|
|
} else {
|
|
expect(deletion).toHaveBeenCalledOnce();
|
|
expect(child).toBeUndefined();
|
|
expect(binding).toBeUndefined();
|
|
expect(link).toEqual(successorLink);
|
|
expect(await loadTranscriptEvents(identity)).toEqual([]);
|
|
if (failure === "successor link" || flow === "adoption") {
|
|
expect(native.archiveThread).not.toHaveBeenCalled();
|
|
} else {
|
|
expect(native.archiveThread).toHaveBeenCalledExactlyOnceWith(
|
|
forkedThread.id,
|
|
failure === "callback"
|
|
? undefined
|
|
: expectDefined(retained, "initializer handle").assertRollbackCurrent,
|
|
);
|
|
}
|
|
if (failure === "native cleanup") {
|
|
expect(native.control.retireConnection).toHaveBeenCalledOnce();
|
|
expect(result.message).toContain("guarded rollback did not complete");
|
|
}
|
|
}
|
|
for (const method of ["assertCurrent", "assertRollbackCurrent"] as const) {
|
|
expect(() => expectDefined(retained, "initializer handle")[method]()).toThrow("closed");
|
|
}
|
|
if (failure === "lost response") {
|
|
await expect(
|
|
withPluginRuntimeRegistryScope(registry, () =>
|
|
rollbackAgentHarnessSessionEntryLifecycle({
|
|
agentId: "main",
|
|
storePath: params.source.storePath,
|
|
target: { canonicalKey: params.targetKey, storeKeys: [params.targetKey] },
|
|
expectedEntry: expectDefined(child, "ready child"),
|
|
expectedSessionId: identity.sessionId,
|
|
expectedUpdatedAt: child!.updatedAt,
|
|
archiveTranscript: true,
|
|
}),
|
|
),
|
|
).rejects.toThrow("owned by supervision");
|
|
expect(loadSessionEntry(identity)).toEqual(child);
|
|
expect(bindingStore.read(identity)).toEqual(binding);
|
|
}
|
|
expect(bindingStore.read(fixture.sourceIdentity)).toEqual(sourceBinding);
|
|
expect(loadSessionEntry(params.source)).toEqual(expectedSourceEntry);
|
|
expect(await loadTranscriptEvents(params.source)).toEqual(sourceHistory);
|
|
expect(native.archiveThread.mock.calls).not.toEqual(
|
|
expect.arrayContaining([expect.arrayContaining([sourceThread.id])]),
|
|
);
|
|
});
|
|
},
|
|
);
|