Files
open-design/packages/contracts/tests/od-next-strategy-v2.test.ts
424f5c68cc fix(od-next): remove output-contract gates and unblock continuation (#8356)
* fix(od-next): continue plans with keyed markers and honor explicit deliverables

* fix(od-next): retire output contracts and hand off continued tasks

* fix(od-next): record round settlement and recover production cold

* fix(od-next): resume marker sessions with context deltas

* fix(od-next): separate completion facts and honor plan-only intent

* fix(od-next): preserve physical failures and legacy chat compatibility

* test(web): align run recovery fixtures and await campaign mount

* fix(od-next): tolerate terminal production marker formatting

* fix(od-next): unblock follow-ups after a stop or unreadable history (#8423)

Treat a previous task this build cannot read as absent so the follow-up starts a new task, and stop counting a Run with a pending cancel as holding its conversation. Add end-to-end coverage for the marker continuation flow and restore the remote Workspace directory gate cases.

* docs: clarify focused local validation

Keep local daemon and web validation scoped to affected files and integration boundaries. Reserve full suites for PR CI unless the user explicitly requests them.

* fix(web): scope folded task completion to its final run

Keep the final physical Run key and conclusion together when OD Next history is folded or accumulated live. Reuse the existing predecessor offsets while retaining the full display history and remaining Todo snapshot.

Add regression coverage for completed and unfinished production, live continuation, multiple Runs, legacy history, wrong and quoted markers, question handoff, and failed or canceled Runs.

Validated the parent regression before the fix, 165 focused Web tests, workspace typecheck, and guard.

* fix(od-next): compact historical context without restricting clarification

Reuse confirmed artifact summaries for old assistant history, preserve cross-agent plans and original requests, and represent earlier questionnaires as neutral historical information without inferring answers.

Keep user replies, skips, changed answers, unpersisted bodies, examples, and unfamiliar content intact. Do not add question repetition checks, output gates, repair rounds, or limits on clarification.

Validated red regressions, shared context and Web provider tests, focused handoff/resume/cold-production coverage, real server and fake CLI input, workspace typecheck, and guard. The repeated-question server fixture remains successful.

---------

Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
Co-authored-by: Cheems <hihicheems@gmail.com>
Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
2026-10-13 10:32:45 +00:00

230 lines
8.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
AgentCapabilitySnapshotV2Schema,
AppliedStrategyBindingV2Schema,
BundledStrategyDeclarationV2Schema,
ChildAgentEvidenceV2Schema,
OD_NEXT_APPLIED_STRATEGY_SCHEMA,
PluginManifestSchema,
StrategyTaskProjectionV2Schema,
} from '../src/index.js';
const hash = 'a'.repeat(64);
describe('OD Next V2 bundled declaration and applied identity', () => {
it('parses the versioned asset declaration without changing legacy manifests', () => {
const legacy = PluginManifestSchema.parse({
name: 'legacy-scenario',
version: '1.0.0',
od: {
kind: 'scenario',
strategy: {
schema: 'community.example/v9',
authorExtension: true,
},
},
});
expect((legacy.od as Record<string, unknown>)['strategy']).toEqual({
schema: 'community.example/v9',
authorExtension: true,
});
const manifest = PluginManifestSchema.parse({
name: 'od-next-strategy',
version: '2.0.0',
od: {
kind: 'scenario',
strategy: {
schema: 'open-design.bundled-strategy/v2',
id: 'od-next-strategy',
promptRecipe: 'od-next-plan-build-v2',
assets: {
core: { path: './assets/core.md', version: '2.0.0' },
orchestration: { path: './assets/orchestration.md', version: '2.0.0' },
taskProfiles: [
{ taskType: 'prototype', path: './profiles/prototype.md', version: '2', rollout: 'active', projectKinds: ['prototype'] },
{ taskType: 'ppt', path: './profiles/ppt.md', version: '2', rollout: 'reserved', projectKinds: ['deck'] },
{ taskType: 'marketing', path: './profiles/marketing.md', version: '2', rollout: 'reserved', projectKinds: ['image'] },
{ taskType: 'hyperframes', path: './profiles/hyperframes.md', version: '2', rollout: 'active', projectKinds: ['video'] },
],
taskProfileMapping: { path: './references/mapping.md', version: '2' },
},
},
},
});
const declaration = BundledStrategyDeclarationV2Schema.parse(
(manifest.od as Record<string, unknown>)['strategy'],
);
expect(declaration.promptRecipe).toBe('od-next-plan-build-v2');
});
it('accepts a complete applied content binding and rejects ambiguous profile identity', () => {
const binding = {
schema: OD_NEXT_APPLIED_STRATEGY_SCHEMA,
id: 'od-next-strategy',
version: '2.0.0',
packageHash: hash,
assetDigests: [
{ path: './assets/core.md', sha256: hash },
{ path: './assets/orchestration.md', sha256: 'b'.repeat(64) },
{ path: './assets/prototype.md', sha256: 'c'.repeat(64) },
],
selectedTaskProfile: {
taskType: 'prototype',
version: 'prototype@2.0.0',
path: './assets/prototype.md',
sha256: 'c'.repeat(64),
},
taskProfileVersions: ['prototype@2.0.0'],
promptRecipe: 'od-next-plan-build-v2',
};
expect(AppliedStrategyBindingV2Schema.parse(binding)).toEqual(binding);
expect(() => AppliedStrategyBindingV2Schema.parse({
...binding,
assetDigests: [binding.assetDigests[0], binding.assetDigests[0]],
})).toThrow(/unique/);
expect(() => AppliedStrategyBindingV2Schema.parse({
...binding,
assetDigests: [...binding.assetDigests].reverse(),
})).toThrow(/stable path order/);
expect(() => AppliedStrategyBindingV2Schema.parse({
...binding,
selectedTaskProfile: {
...binding.selectedTaskProfile,
sha256: 'd'.repeat(64),
},
})).toThrow(/digest must match/);
});
});
describe('OD Next V2 capability, Child, and task projection contracts', () => {
it('requires structured evidence before native Child support is verified', () => {
expect(AgentCapabilitySnapshotV2Schema.parse({
agentId: 'codex',
agentVersion: '1.0.0',
nativeSessionContinuation: 'verified',
nativeSubagents: {
support: 'verified',
evidenceLevel: 'structured',
source: 'fixture:codex-v1',
},
capturedAt: 1,
}).nativeSubagents.support).toBe('verified');
expect(() => AgentCapabilitySnapshotV2Schema.parse({
agentId: 'codex',
nativeSessionContinuation: 'verified',
nativeSubagents: {
support: 'verified',
evidenceLevel: 'tool_only',
source: 'self-report',
},
capturedAt: 1,
})).toThrow(/structured/);
});
it('parses structured Child lifecycle facts without assigning result quality', () => {
expect(ChildAgentEvidenceV2Schema.parse({
childId: 'child-1',
parentId: 'run-1',
packageId: 'shell',
state: 'completed',
source: 'codex-jsonl',
sourceEventType: 'task_complete',
startedAt: 10,
endedAt: 20,
}).state).toBe('completed');
expect(ChildAgentEvidenceV2Schema.parse({
childId: 'child-without-clock',
state: 'failed',
source: 'opencode-event',
sourceEventType: 'session.error',
}).endedAt).toBeUndefined();
});
it('keeps the optional task projection internally consistent', () => {
const projection = {
taskExecutionId: 'task-1',
strategy: {
id: 'od-next-strategy',
version: '2.0.0',
packageHash: hash,
snapshotId: 'snapshot-1',
},
inputStage: 'request',
outcome: 'plan_ready',
route: 'full_plan',
executionMode: 'simple',
activeRunId: 'run-plan',
nextRunId: 'run-production',
terminal: false,
};
expect(StrategyTaskProjectionV2Schema.parse(projection)).toEqual(projection);
for (const [legacy, reason] of [['production_ready', 'continued'], ['run_failed', 'ended'], ['todo_unfinished', 'ended']]) {
expect(StrategyTaskProjectionV2Schema.parse({ ...projection, settlementReason: legacy }).settlementReason).toBe(reason);
}
const mapped = { ...projection, runMappings: [
{ runId: 'run-plan', taskRunIndex: 0 },
{ runId: 'run-production', taskRunIndex: 1 },
] };
expect(StrategyTaskProjectionV2Schema.parse(mapped)).toEqual(mapped);
for (const taskRunIndex of [-1, 0.5]) {
expect(() => StrategyTaskProjectionV2Schema.parse({
...projection, runMappings: [{ runId: 'run-plan', taskRunIndex }],
})).toThrow();
}
expect(() => StrategyTaskProjectionV2Schema.parse({
...projection,
outcome: 'completed',
terminal: false,
})).toThrow(/terminal/);
expect(StrategyTaskProjectionV2Schema.parse({
...projection, inputStage: 'request', outcome: 'completed', route: null,
executionMode: null, nextRunId: undefined, terminal: true, deliverableValid: false,
})).toMatchObject({ outcome: 'completed', deliverableValid: false });
});
});
describe('task profile resources', () => {
const profile = (resources: unknown) => ({
schema: 'open-design.bundled-strategy/v2',
id: 'od-next-strategy',
promptRecipe: 'od-next-plan-build-v2',
assets: {
core: { path: './assets/core.md', version: '2.0.0' },
orchestration: { path: './assets/orchestration.md', version: '2.0.0' },
taskProfiles: [
{ taskType: 'prototype', path: './profiles/prototype.md', version: '2.1.0', rollout: 'active', projectKinds: ['prototype'], resources },
{ taskType: 'ppt', path: './profiles/ppt.md', version: '2', rollout: 'reserved', projectKinds: ['deck'] },
{ taskType: 'marketing', path: './profiles/marketing.md', version: '2', rollout: 'reserved', projectKinds: ['image'] },
{ taskType: 'hyperframes', path: './profiles/hyperframes.md', version: '2', rollout: 'active', projectKinds: ['video'] },
],
taskProfileMapping: { path: './references/mapping.md', version: '2' },
},
});
it('accepts declared shell resources on one profile and leaves the others bare', () => {
const declaration = BundledStrategyDeclarationV2Schema.parse(profile([
{ path: './profiles/prototype/device-frames/iphone.html', version: '1.0.0' },
{ path: './profiles/prototype/device-frames/android.html', version: '1.0.0' },
]));
expect(declaration.assets.taskProfiles[0]?.resources).toHaveLength(2);
expect(declaration.assets.taskProfiles[1]?.resources).toBeUndefined();
});
it('rejects duplicate resource paths and a resource that re-declares the profile itself', () => {
expect(() => BundledStrategyDeclarationV2Schema.parse(profile([
{ path: './profiles/prototype/device-frames/iphone.html', version: '1.0.0' },
{ path: './profiles/prototype/device-frames/iphone.html', version: '1.0.1' },
]))).toThrow(/unique/i);
expect(() => BundledStrategyDeclarationV2Schema.parse(profile([
{ path: './profiles/prototype.md', version: '1.0.0' },
]))).toThrow(/unique/i);
expect(() => BundledStrategyDeclarationV2Schema.parse(profile([
{ path: '../escape.html', version: '1.0.0' },
]))).toThrow();
});
});