@devflow-tools/delivery-line 0.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/__tests__/artifact-generation.test.d.ts +2 -0
- package/dist/__tests__/artifact-generation.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-generation.test.js +16 -0
- package/dist/__tests__/artifact-generation.test.js.map +1 -0
- package/dist/__tests__/backend-scenario.e2e.test.d.ts +2 -0
- package/dist/__tests__/backend-scenario.e2e.test.d.ts.map +1 -0
- package/dist/__tests__/backend-scenario.e2e.test.js +25 -0
- package/dist/__tests__/backend-scenario.e2e.test.js.map +1 -0
- package/dist/__tests__/claude-events.test.d.ts +2 -0
- package/dist/__tests__/claude-events.test.d.ts.map +1 -0
- package/dist/__tests__/claude-events.test.js +28 -0
- package/dist/__tests__/claude-events.test.js.map +1 -0
- package/dist/__tests__/facade-e2e.test.d.ts +2 -0
- package/dist/__tests__/facade-e2e.test.d.ts.map +1 -0
- package/dist/__tests__/facade-e2e.test.js +167 -0
- package/dist/__tests__/facade-e2e.test.js.map +1 -0
- package/dist/__tests__/frontend-scenario.e2e.test.d.ts +2 -0
- package/dist/__tests__/frontend-scenario.e2e.test.d.ts.map +1 -0
- package/dist/__tests__/frontend-scenario.e2e.test.js +35 -0
- package/dist/__tests__/frontend-scenario.e2e.test.js.map +1 -0
- package/dist/__tests__/git-artifact-store.test.d.ts +2 -0
- package/dist/__tests__/git-artifact-store.test.d.ts.map +1 -0
- package/dist/__tests__/git-artifact-store.test.js +44 -0
- package/dist/__tests__/git-artifact-store.test.js.map +1 -0
- package/dist/__tests__/governance-adapters.test.d.ts +2 -0
- package/dist/__tests__/governance-adapters.test.d.ts.map +1 -0
- package/dist/__tests__/governance-adapters.test.js +51 -0
- package/dist/__tests__/governance-adapters.test.js.map +1 -0
- package/dist/__tests__/handoff-validator.test.d.ts +2 -0
- package/dist/__tests__/handoff-validator.test.d.ts.map +1 -0
- package/dist/__tests__/handoff-validator.test.js +13 -0
- package/dist/__tests__/handoff-validator.test.js.map +1 -0
- package/dist/__tests__/interaction-session.test.d.ts +2 -0
- package/dist/__tests__/interaction-session.test.d.ts.map +1 -0
- package/dist/__tests__/interaction-session.test.js +13 -0
- package/dist/__tests__/interaction-session.test.js.map +1 -0
- package/dist/__tests__/interaction.test.d.ts +2 -0
- package/dist/__tests__/interaction.test.d.ts.map +1 -0
- package/dist/__tests__/interaction.test.js +27 -0
- package/dist/__tests__/interaction.test.js.map +1 -0
- package/dist/__tests__/persistence.test.d.ts +2 -0
- package/dist/__tests__/persistence.test.d.ts.map +1 -0
- package/dist/__tests__/persistence.test.js +41 -0
- package/dist/__tests__/persistence.test.js.map +1 -0
- package/dist/__tests__/recovery-spike.test.d.ts +2 -0
- package/dist/__tests__/recovery-spike.test.d.ts.map +1 -0
- package/dist/__tests__/recovery-spike.test.js +20 -0
- package/dist/__tests__/recovery-spike.test.js.map +1 -0
- package/dist/__tests__/sqlite-recovery.test.d.ts +2 -0
- package/dist/__tests__/sqlite-recovery.test.d.ts.map +1 -0
- package/dist/__tests__/sqlite-recovery.test.js +90 -0
- package/dist/__tests__/sqlite-recovery.test.js.map +1 -0
- package/dist/__tests__/state-machine.test.d.ts +2 -0
- package/dist/__tests__/state-machine.test.d.ts.map +1 -0
- package/dist/__tests__/state-machine.test.js +49 -0
- package/dist/__tests__/state-machine.test.js.map +1 -0
- package/dist/__tests__/subagent-orchestrator.test.d.ts +2 -0
- package/dist/__tests__/subagent-orchestrator.test.d.ts.map +1 -0
- package/dist/__tests__/subagent-orchestrator.test.js +33 -0
- package/dist/__tests__/subagent-orchestrator.test.js.map +1 -0
- package/dist/__tests__/subagent-recovery.e2e.test.d.ts +2 -0
- package/dist/__tests__/subagent-recovery.e2e.test.d.ts.map +1 -0
- package/dist/__tests__/subagent-recovery.e2e.test.js +35 -0
- package/dist/__tests__/subagent-recovery.e2e.test.js.map +1 -0
- package/dist/__tests__/task-dag.test.d.ts +2 -0
- package/dist/__tests__/task-dag.test.d.ts.map +1 -0
- package/dist/__tests__/task-dag.test.js +31 -0
- package/dist/__tests__/task-dag.test.js.map +1 -0
- package/dist/__tests__/worker-integration.test.d.ts +2 -0
- package/dist/__tests__/worker-integration.test.d.ts.map +1 -0
- package/dist/__tests__/worker-integration.test.js +217 -0
- package/dist/__tests__/worker-integration.test.js.map +1 -0
- package/dist/adapters/approval.d.ts +40 -0
- package/dist/adapters/approval.d.ts.map +1 -0
- package/dist/adapters/approval.js +100 -0
- package/dist/adapters/approval.js.map +1 -0
- package/dist/adapters/delivery-package.d.ts +23 -0
- package/dist/adapters/delivery-package.d.ts.map +1 -0
- package/dist/adapters/delivery-package.js +37 -0
- package/dist/adapters/delivery-package.js.map +1 -0
- package/dist/adapters/integration.d.ts +37 -0
- package/dist/adapters/integration.d.ts.map +1 -0
- package/dist/adapters/integration.js +84 -0
- package/dist/adapters/integration.js.map +1 -0
- package/dist/adapters/review.d.ts +12 -0
- package/dist/adapters/review.d.ts.map +1 -0
- package/dist/adapters/review.js +33 -0
- package/dist/adapters/review.js.map +1 -0
- package/dist/adapters/verification.d.ts +25 -0
- package/dist/adapters/verification.d.ts.map +1 -0
- package/dist/adapters/verification.js +57 -0
- package/dist/adapters/verification.js.map +1 -0
- package/dist/adapters/workflow-execution.d.ts +130 -0
- package/dist/adapters/workflow-execution.d.ts.map +1 -0
- package/dist/adapters/workflow-execution.js +135 -0
- package/dist/adapters/workflow-execution.js.map +1 -0
- package/dist/artifact-generation.test.d.ts +2 -0
- package/dist/artifact-generation.test.d.ts.map +1 -0
- package/dist/artifact-generation.test.js +11 -0
- package/dist/artifact-generation.test.js.map +1 -0
- package/dist/artifacts/context-manifest.d.ts +20 -0
- package/dist/artifacts/context-manifest.d.ts.map +1 -0
- package/dist/artifacts/context-manifest.js +24 -0
- package/dist/artifacts/context-manifest.js.map +1 -0
- package/dist/artifacts/generation-guard.d.ts +13 -0
- package/dist/artifacts/generation-guard.d.ts.map +1 -0
- package/dist/artifacts/generation-guard.js +16 -0
- package/dist/artifacts/generation-guard.js.map +1 -0
- package/dist/artifacts/git-artifact-store.d.ts +16 -0
- package/dist/artifacts/git-artifact-store.d.ts.map +1 -0
- package/dist/artifacts/git-artifact-store.js +47 -0
- package/dist/artifacts/git-artifact-store.js.map +1 -0
- package/dist/claude-events.test.d.ts +2 -0
- package/dist/claude-events.test.d.ts.map +1 -0
- package/dist/claude-events.test.js +14 -0
- package/dist/claude-events.test.js.map +1 -0
- package/dist/composition.d.ts +13 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +47 -0
- package/dist/composition.js.map +1 -0
- package/dist/domain/interaction.d.ts +23 -0
- package/dist/domain/interaction.d.ts.map +1 -0
- package/dist/domain/interaction.js +60 -0
- package/dist/domain/interaction.js.map +1 -0
- package/dist/domain/state-machine.d.ts +26 -0
- package/dist/domain/state-machine.d.ts.map +1 -0
- package/dist/domain/state-machine.js +67 -0
- package/dist/domain/state-machine.js.map +1 -0
- package/dist/domain/task-dag.d.ts +5 -0
- package/dist/domain/task-dag.d.ts.map +1 -0
- package/dist/domain/task-dag.js +78 -0
- package/dist/domain/task-dag.js.map +1 -0
- package/dist/domain/types.d.ts +320 -0
- package/dist/domain/types.d.ts.map +1 -0
- package/dist/domain/types.js +9 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/facade/delivery-line-facade.d.ts +77 -0
- package/dist/facade/delivery-line-facade.d.ts.map +1 -0
- package/dist/facade/delivery-line-facade.js +417 -0
- package/dist/facade/delivery-line-facade.js.map +1 -0
- package/dist/git-artifact-store.test.d.ts +2 -0
- package/dist/git-artifact-store.test.d.ts.map +1 -0
- package/dist/git-artifact-store.test.js +27 -0
- package/dist/git-artifact-store.test.js.map +1 -0
- package/dist/governance-adapters.test.d.ts +2 -0
- package/dist/governance-adapters.test.d.ts.map +1 -0
- package/dist/governance-adapters.test.js +14 -0
- package/dist/governance-adapters.test.js.map +1 -0
- package/dist/handoff-validator.test.d.ts +2 -0
- package/dist/handoff-validator.test.d.ts.map +1 -0
- package/dist/handoff-validator.test.js +10 -0
- package/dist/handoff-validator.test.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction-session.d.ts +12 -0
- package/dist/interaction-session.d.ts.map +1 -0
- package/dist/interaction-session.js +21 -0
- package/dist/interaction-session.js.map +1 -0
- package/dist/interaction-session.test.d.ts +2 -0
- package/dist/interaction-session.test.d.ts.map +1 -0
- package/dist/interaction-session.test.js +13 -0
- package/dist/interaction-session.test.js.map +1 -0
- package/dist/persistence/cas.d.ts +4 -0
- package/dist/persistence/cas.d.ts.map +1 -0
- package/dist/persistence/cas.js +7 -0
- package/dist/persistence/cas.js.map +1 -0
- package/dist/persistence/event-ledger.d.ts +10 -0
- package/dist/persistence/event-ledger.d.ts.map +1 -0
- package/dist/persistence/event-ledger.js +6 -0
- package/dist/persistence/event-ledger.js.map +1 -0
- package/dist/persistence/memory-store.d.ts +51 -0
- package/dist/persistence/memory-store.d.ts.map +1 -0
- package/dist/persistence/memory-store.js +99 -0
- package/dist/persistence/memory-store.js.map +1 -0
- package/dist/persistence/schema.d.ts +2 -0
- package/dist/persistence/schema.d.ts.map +1 -0
- package/dist/persistence/schema.js +107 -0
- package/dist/persistence/schema.js.map +1 -0
- package/dist/persistence/sqlite-store.d.ts +59 -0
- package/dist/persistence/sqlite-store.d.ts.map +1 -0
- package/dist/persistence/sqlite-store.js +133 -0
- package/dist/persistence/sqlite-store.js.map +1 -0
- package/dist/persistence.test.d.ts +2 -0
- package/dist/persistence.test.d.ts.map +1 -0
- package/dist/persistence.test.js +28 -0
- package/dist/persistence.test.js.map +1 -0
- package/dist/ports/index.d.ts +248 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/recovery-spike.d.ts +22 -0
- package/dist/recovery-spike.d.ts.map +1 -0
- package/dist/recovery-spike.js +28 -0
- package/dist/recovery-spike.js.map +1 -0
- package/dist/recovery-spike.test.d.ts +2 -0
- package/dist/recovery-spike.test.d.ts.map +1 -0
- package/dist/recovery-spike.test.js +20 -0
- package/dist/recovery-spike.test.js.map +1 -0
- package/dist/runtime/claude-host-adapter.d.ts +28 -0
- package/dist/runtime/claude-host-adapter.d.ts.map +1 -0
- package/dist/runtime/claude-host-adapter.js +43 -0
- package/dist/runtime/claude-host-adapter.js.map +1 -0
- package/dist/runtime/context-binder.d.ts +61 -0
- package/dist/runtime/context-binder.d.ts.map +1 -0
- package/dist/runtime/context-binder.js +26 -0
- package/dist/runtime/context-binder.js.map +1 -0
- package/dist/runtime/execution-coordinator.d.ts +32 -0
- package/dist/runtime/execution-coordinator.d.ts.map +1 -0
- package/dist/runtime/execution-coordinator.js +243 -0
- package/dist/runtime/execution-coordinator.js.map +1 -0
- package/dist/runtime/gate-queue.d.ts +17 -0
- package/dist/runtime/gate-queue.d.ts.map +1 -0
- package/dist/runtime/gate-queue.js +33 -0
- package/dist/runtime/gate-queue.js.map +1 -0
- package/dist/runtime/host-conformance.d.ts +47 -0
- package/dist/runtime/host-conformance.d.ts.map +1 -0
- package/dist/runtime/host-conformance.js +61 -0
- package/dist/runtime/host-conformance.js.map +1 -0
- package/dist/runtime/host-routing.d.ts +5 -0
- package/dist/runtime/host-routing.d.ts.map +1 -0
- package/dist/runtime/host-routing.js +42 -0
- package/dist/runtime/host-routing.js.map +1 -0
- package/dist/runtime/memory-writeback.d.ts +18 -0
- package/dist/runtime/memory-writeback.d.ts.map +1 -0
- package/dist/runtime/memory-writeback.js +23 -0
- package/dist/runtime/memory-writeback.js.map +1 -0
- package/dist/runtime/operation-journal.d.ts +13 -0
- package/dist/runtime/operation-journal.d.ts.map +1 -0
- package/dist/runtime/operation-journal.js +38 -0
- package/dist/runtime/operation-journal.js.map +1 -0
- package/dist/runtime/plan-compiler.d.ts +37 -0
- package/dist/runtime/plan-compiler.d.ts.map +1 -0
- package/dist/runtime/plan-compiler.js +62 -0
- package/dist/runtime/plan-compiler.js.map +1 -0
- package/dist/runtime/project-runtime-factory.d.ts +15 -0
- package/dist/runtime/project-runtime-factory.d.ts.map +1 -0
- package/dist/runtime/project-runtime-factory.js +11 -0
- package/dist/runtime/project-runtime-factory.js.map +1 -0
- package/dist/runtime/review-coordinator.d.ts +25 -0
- package/dist/runtime/review-coordinator.d.ts.map +1 -0
- package/dist/runtime/review-coordinator.js +22 -0
- package/dist/runtime/review-coordinator.js.map +1 -0
- package/dist/subagents/claude-events.d.ts +59 -0
- package/dist/subagents/claude-events.d.ts.map +1 -0
- package/dist/subagents/claude-events.js +60 -0
- package/dist/subagents/claude-events.js.map +1 -0
- package/dist/subagents/handoff-validator.d.ts +8 -0
- package/dist/subagents/handoff-validator.d.ts.map +1 -0
- package/dist/subagents/handoff-validator.js +20 -0
- package/dist/subagents/handoff-validator.js.map +1 -0
- package/dist/subagents/orchestrator.d.ts +47 -0
- package/dist/subagents/orchestrator.d.ts.map +1 -0
- package/dist/subagents/orchestrator.js +85 -0
- package/dist/subagents/orchestrator.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { assertApproval } from '../adapters/approval.js';
|
|
3
|
+
import { assertIntegrationTarget, freezeIntegration } from '../adapters/integration.js';
|
|
4
|
+
import { assertReviewPasses } from '../adapters/review.js';
|
|
5
|
+
import { LocalDeliveryPackageAdapter, verifyDeliveryPackage } from '../adapters/delivery-package.js';
|
|
6
|
+
import { PolicyApprovalAdapter, assertNonBypassableGates, assertWaiver, assertRequiredApprovals, loadApprovalPolicy, loadApprovalOwners, requiredApprovalRoles } from '../adapters/approval.js';
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
describe('delivery governance adapters', () => {
|
|
9
|
+
it('requires independent approval, complete reviews, and candidate integration ref', () => {
|
|
10
|
+
const actor = { id: 'owner', kind: 'human', role: 'code-owner' };
|
|
11
|
+
expect(() => assertApproval({ id: 'a', stage: 'review', actor, policyVersion: '1', artifactHash: 'h', decision: 'approved', reason: 'ok', createdAt: 1 }, { id: 'agent', kind: 'agent', role: 'implementer' }, 'h')).not.toThrow();
|
|
12
|
+
expect(() => assertIntegrationTarget({ integrationRef: 'refs/devflow/c', targetRef: 'main', candidateRevision: 'abc' })).not.toThrow();
|
|
13
|
+
const receipts = ['bugs', 'security', 'compliance'].map(kind => ({ id: kind, kind, artifactHash: 'h', findings: [], passed: true, createdAt: 1 }));
|
|
14
|
+
expect(() => assertReviewPasses(receipts, 'h')).not.toThrow();
|
|
15
|
+
expect(freezeIntegration({ baseRef: 'base', integrationRef: 'refs/devflow/c', targetRef: 'main', candidateRevision: 'abc' })).toMatchObject({ candidateRevision: 'abc' });
|
|
16
|
+
expect(() => freezeIntegration({ baseRef: 'main', integrationRef: 'refs/devflow/c', targetRef: 'main', candidateRevision: 'abc' })).toThrow('distinct');
|
|
17
|
+
});
|
|
18
|
+
it('composes policy approval and local package adapters', async () => {
|
|
19
|
+
const actor = { id: 'owner', kind: 'human', role: 'code-owner' };
|
|
20
|
+
const approval = new PolicyApprovalAdapter({ id: 'agent', kind: 'agent', role: 'implementer' }, 'delivery-line.v1', ['code-owner']);
|
|
21
|
+
await expect(approval.approve({ id: 'a', stage: 'review', actor, policyVersion: 'delivery-line.v1', artifactHash: 'h', decision: 'approved', reason: 'ok', createdAt: 1 })).resolves.toMatchObject({ decision: 'approved' });
|
|
22
|
+
await expect(approval.approve({ id: 'expired', stage: 'review', actor, policyVersion: 'delivery-line.v1', artifactHash: 'h', decision: 'approved', reason: 'ok', createdAt: 1, expiresAt: 1 })).rejects.toThrow('expired');
|
|
23
|
+
expect(() => assertNonBypassableGates({ auth: true, dataIntegrity: true, redTest: true })).not.toThrow();
|
|
24
|
+
expect(() => assertNonBypassableGates({ auth: false, dataIntegrity: true, redTest: true })).toThrow('safety');
|
|
25
|
+
expect(() => assertWaiver({ id: 'w', kind: 'waiver', question: 'waive', options: [], decision: 'allow', decidedBy: actor, artifactHash: 'h', expiresAt: Date.now() + 1000 }, 'h')).not.toThrow();
|
|
26
|
+
const pkg = new LocalDeliveryPackageAdapter();
|
|
27
|
+
const exported = await pkg.prepare({ record: { id: 'case', stage: 'release_pending', baseRef: 'main', integrationRef: 'refs/devflow/delivery/case', targetRef: 'main', artifacts: [], tasks: [], approvals: [], decisions: [], evidence: [] }, path: '/tmp/delivery-line-package.json' });
|
|
28
|
+
expect(exported).toMatchObject({ path: '/tmp/delivery-line-package.json' });
|
|
29
|
+
expect(() => pkg.verify?.({ path: exported.path, packageHash: exported.packageHash })).not.toThrow();
|
|
30
|
+
expect(() => verifyDeliveryPackage(exported.path, exported.packageHash)).not.toThrow();
|
|
31
|
+
expect(() => verifyDeliveryPackage(exported.path, 'stale')).toThrow('HASH_MISMATCH');
|
|
32
|
+
});
|
|
33
|
+
it('loads versioned project policy and requires every risk-specific owner', () => {
|
|
34
|
+
const policy = loadApprovalPolicy(resolve(process.cwd(), '../../.devflow/delivery-line/approval-policy.yaml'));
|
|
35
|
+
expect(policy.version).toBe('delivery-line.v1');
|
|
36
|
+
expect(requiredApprovalRoles(policy, 'high', 'review')).toEqual(['code_owner', 'security_owner']);
|
|
37
|
+
const approvals = [
|
|
38
|
+
{ id: 'c', stage: 'review', actor: { id: 'c', kind: 'human', role: 'code_owner' }, policyVersion: policy.version, artifactHash: 'h', decision: 'approved', reason: 'ok', createdAt: Date.now() },
|
|
39
|
+
{ id: 's', stage: 'review', actor: { id: 's', kind: 'human', role: 'security_owner' }, policyVersion: policy.version, artifactHash: 'h', decision: 'approved', reason: 'ok', createdAt: Date.now() },
|
|
40
|
+
];
|
|
41
|
+
expect(() => assertRequiredApprovals({ policy, riskTier: 'high', stage: 'review', approvals, artifactHash: 'h' })).not.toThrow();
|
|
42
|
+
expect(() => assertRequiredApprovals({ policy, riskTier: 'high', stage: 'review', approvals: approvals.slice(0, 1), artifactHash: 'h' })).toThrow('security_owner');
|
|
43
|
+
});
|
|
44
|
+
it('rejects an actor that is not assigned to the approval role', async () => {
|
|
45
|
+
const owners = loadApprovalOwners(resolve(process.cwd(), '../../.devflow/delivery-line/owners.yaml'));
|
|
46
|
+
const adapter = new PolicyApprovalAdapter({ id: 'agent', kind: 'agent' }, 'delivery-line.v1', [], undefined, { code_owner: ['alice'] });
|
|
47
|
+
await expect(adapter.approve({ id: 'a', stage: 'review', actor: { id: 'bob', kind: 'human', role: 'code_owner' }, policyVersion: 'delivery-line.v1', artifactHash: 'h', decision: 'approved', reason: 'ok', createdAt: Date.now() })).rejects.toThrow('assigned owner');
|
|
48
|
+
expect(owners).toHaveProperty('code_owner');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
//# sourceMappingURL=governance-adapters.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governance-adapters.test.js","sourceRoot":"","sources":["../../src/__tests__/governance-adapters.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACrG,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,YAAY,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChM,OAAO,EAAQ,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACxF,MAAM,KAAK,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;QAC1E,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACnO,MAAM,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,EAAE,cAAc,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACvI,MAAM,QAAQ,GAAI,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9J,MAAM,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC9D,MAAM,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1K,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1J,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,KAAK,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;QAC1E,MAAM,QAAQ,GAAG,IAAI,qBAAqB,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,kBAAkB,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;QACpI,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;QAC7N,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC3N,MAAM,CAAC,GAAG,EAAE,CAAC,wBAAwB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACzG,MAAM,CAAC,GAAG,EAAE,CAAC,wBAAwB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9G,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjM,MAAM,GAAG,GAAG,IAAI,2BAA2B,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,4BAA4B,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,iCAAiC,EAAE,CAAC,CAAC;QAC1R,MAAM,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,iCAAiC,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACrG,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACvF,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,mDAAmD,CAAC,CAAC,CAAC;QAC/G,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAChD,MAAM,CAAC,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAClG,MAAM,SAAS,GAAG;YAChB,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAiB,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAmB,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;YAC3N,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAiB,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,OAAgB,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAmB,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE;SAChO,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjI,MAAM,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtK,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,0CAA0C,CAAC,CAAC,CAAC;QACtG,MAAM,OAAO,GAAG,IAAI,qBAAqB,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACxI,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACxQ,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff-validator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/handoff-validator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { validateAgentHandoff } from '../subagents/handoff-validator.js';
|
|
3
|
+
const handoff = { caseId: 'case', from: { role: 'designer', agentId: 'a1', version: 'v1', sessionId: 's1' }, to: { role: 'planner', agentId: 'a2', version: 'v1' }, inputRefs: ['spec-hash'], contextReceipt: 'ctx', claims: [{ statement: 'scope verified', evidenceRefs: ['e1'] }], outputRefs: ['plan-hash'], status: 'complete', idempotencyKey: 'h1' };
|
|
4
|
+
describe('handoff validation', () => {
|
|
5
|
+
it('accepts evidence-bound handoffs and rejects untrusted control changes', () => {
|
|
6
|
+
expect(() => validateAgentHandoff(handoff, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec-hash'] })).not.toThrow();
|
|
7
|
+
expect(() => validateAgentHandoff({ ...handoff, outputRefs: ['targetRef'] }, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec-hash'] })).toThrow('protected');
|
|
8
|
+
expect(() => validateAgentHandoff({ ...handoff, from: { ...handoff.from, role: 'unknown' } }, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec-hash'] })).toThrow('not allowed');
|
|
9
|
+
expect(() => validateAgentHandoff({ ...handoff, status: 'invalid' }, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec-hash'] })).toThrow('evidence');
|
|
10
|
+
expect(() => validateAgentHandoff({ ...handoff, transcript: 'hidden' }, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec-hash'] })).toThrow('forbidden');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=handoff-validator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff-validator.test.js","sourceRoot":"","sources":["../../src/__tests__/handoff-validator.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,MAAM,OAAO,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,UAAmB,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;AACrW,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC/H,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACxK,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAC3L,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,SAAkB,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACxK,MAAM,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9K,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/interaction-session.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { InteractionSession } from '../interaction-session.js';
|
|
3
|
+
import { DeliveryLineError } from '../domain/types.js';
|
|
4
|
+
describe('interaction session', () => {
|
|
5
|
+
it('requires one answer before the next blocking question and enforces budget', () => {
|
|
6
|
+
const session = new InteractionSession({ phase: 'clarification', status: 'active', turn: 0, openQuestions: [], blockingUnknowns: [], summaryRefs: [] }, 1);
|
|
7
|
+
session.ask('Who owns the decision?');
|
|
8
|
+
expect(() => session.ask('What is the error contract?')).toThrow(DeliveryLineError);
|
|
9
|
+
session.answer(1);
|
|
10
|
+
expect(() => session.ask('What is the error contract?')).toThrow('interaction budget exceeded');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=interaction-session.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction-session.test.js","sourceRoot":"","sources":["../../src/__tests__/interaction-session.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACnF,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3J,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACpF,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC,CAAC,OAAO,CAAC,6BAA6B,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/interaction.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { addNonBlockingUnknown, answerInteraction, appendInteractionSummary, cancelInteraction, createExecutionInteractionRequest, pauseInteraction, reviseInteraction, validateDecision, validateWaiver } from '../domain/interaction.js';
|
|
3
|
+
const state = { phase: 'clarification', status: 'active', turn: 1, openQuestions: [], blockingUnknowns: [], summaryRefs: [] };
|
|
4
|
+
describe('delivery interaction and decision contracts', () => {
|
|
5
|
+
it('supports pause, revise, answer and cancel without approving implicitly', () => {
|
|
6
|
+
const revised = reviseInteraction(state, 'Which scope?', 2);
|
|
7
|
+
expect(revised.status).toBe('awaiting_user');
|
|
8
|
+
expect(answerInteraction(revised, 2).status).toBe('active');
|
|
9
|
+
expect(pauseInteraction(state).status).toBe('paused');
|
|
10
|
+
expect(cancelInteraction(revised).openQuestions).toEqual([]);
|
|
11
|
+
});
|
|
12
|
+
it('validates decision categories and expiry independently of approval', () => {
|
|
13
|
+
expect(() => validateDecision({ id: 'd', kind: 'technical', question: 'scope?', options: ['a'], decision: 'a', decidedBy: { id: 'u', kind: 'human' }, artifactHash: 'h' })).not.toThrow();
|
|
14
|
+
expect(() => validateDecision({ id: 'w', kind: 'waiver', question: 'skip?', options: ['yes'], decision: 'yes', decidedBy: { id: 'u', kind: 'human' }, artifactHash: 'h', expiresAt: 1 }, 2)).toThrow('expired');
|
|
15
|
+
expect(() => validateWaiver({ id: 'w', kind: 'waiver', question: 'skip?', options: ['yes'], decision: 'yes', decidedBy: { id: 'u', kind: 'human' }, artifactHash: 'old', expiresAt: 10 }, 'current', 2)).toThrow('current artifact');
|
|
16
|
+
expect(() => validateWaiver({ id: 'w', kind: 'waiver', question: 'skip?', options: ['yes'], decision: 'yes', decidedBy: { id: 'u', kind: 'human' }, artifactHash: 'current', expiresAt: 10 }, 'current', 2)).not.toThrow();
|
|
17
|
+
});
|
|
18
|
+
it('records structured summaries and owner-bound non-blocking unknowns', () => {
|
|
19
|
+
const summary = { id: 'summary-1', phase: 'clarification', turn: 1, userStatement: 'Need a bounded change', contextReceipt: 'ctx', actor: { id: 'u', kind: 'human' } };
|
|
20
|
+
const withSummary = appendInteractionSummary(state, summary);
|
|
21
|
+
expect(withSummary.summaryRefs).toEqual(['summary-1']);
|
|
22
|
+
const withUnknown = addNonBlockingUnknown(withSummary, { question: 'Which browser?', owner: { id: 'u', kind: 'human' }, dueAt: Date.now() + 1000 });
|
|
23
|
+
expect(withUnknown.nonBlockingUnknowns?.[0].owner.id).toBe('u');
|
|
24
|
+
expect(createExecutionInteractionRequest({ ...state, phase: 'execution' }, 'task-1', 'Which API contract is authoritative?')).toMatchObject({ taskId: 'task-1', blocking: true, turn: 2 });
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=interaction.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.test.js","sourceRoot":"","sources":["../../src/__tests__/interaction.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/persistence.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { SQLiteCaseStore } from '../persistence/sqlite-store.js';
|
|
6
|
+
import { createDeliveryLineFacade } from '../composition.js';
|
|
7
|
+
function caseRecord() {
|
|
8
|
+
return { id: 'case-1', projectRoot: '/repo', baseRef: 'main', integrationRef: 'refs/devflow/case-1', targetRef: 'main', title: 'demo', source: 'human', riskTier: 'low', autonomy: 'manual', stage: 'draft', budgets: { maxInteractionTurns: 4, maxTaskAttempts: 2, maxWallTimeMs: 1000, maxToolCalls: 10 }, artifacts: [], tasks: [], approvals: [], decisions: [], evidence: [], interaction: { phase: 'clarification', status: 'active', turn: 0, openQuestions: [], blockingUnknowns: [], summaryRefs: [] }, createdBy: { id: 'u', kind: 'human' }, updatedAt: 1, version: 0 };
|
|
9
|
+
}
|
|
10
|
+
describe('delivery line sqlite persistence', () => {
|
|
11
|
+
it('persists cases, rejects stale CAS, deduplicates events, and fences leases', async () => {
|
|
12
|
+
const store = new SQLiteCaseStore(join(mkdtempSync(join(tmpdir(), 'delivery-line-')), 'state.db'));
|
|
13
|
+
const record = caseRecord();
|
|
14
|
+
await store.create(record);
|
|
15
|
+
const next = { ...record, version: 1, stage: 'clarifying', updatedAt: 2 };
|
|
16
|
+
await store.save(next, 0);
|
|
17
|
+
await expect(store.save({ ...next, version: 2 }, 0)).rejects.toMatchObject({ code: 'STATE_VERSION_CONFLICT' });
|
|
18
|
+
const event = { id: 'event-1', caseId: record.id, type: 'case.created', idempotencyKey: 'event-key', actor: record.createdBy, payload: { stage: 'clarifying' }, createdAt: 2 };
|
|
19
|
+
await store.appendEvent(event);
|
|
20
|
+
await store.appendEvent(event);
|
|
21
|
+
await expect(store.appendEvent({ ...event, payload: { stage: 'draft' } })).rejects.toMatchObject({ code: 'IDEMPOTENCY_CONFLICT' });
|
|
22
|
+
expect(store.acquireTaskLease({ taskId: 'task-1', caseId: record.id, owner: 'worker-a', now: 10, leaseMs: 100 })).toBe(true);
|
|
23
|
+
expect(store.acquireTaskLease({ taskId: 'task-1', caseId: record.id, owner: 'worker-b', now: 50, leaseMs: 100 })).toBe(false);
|
|
24
|
+
expect(store.acquireTaskLease({ taskId: 'task-1', caseId: record.id, owner: 'worker-b', now: 111, leaseMs: 100 })).toBe(true);
|
|
25
|
+
expect(store.releaseTaskLease('task-1', 'worker-b')).toBe(true);
|
|
26
|
+
store.close();
|
|
27
|
+
});
|
|
28
|
+
it('uses one project-local composition and leaves execution explicitly unavailable', async () => {
|
|
29
|
+
const root = mkdtempSync(join(tmpdir(), 'delivery-composition-'));
|
|
30
|
+
const facade = createDeliveryLineFacade(root);
|
|
31
|
+
const identity = { actor: { id: 'user', kind: 'human' }, sessionId: 's', executionId: 'e', contextReceipt: 'ctx' };
|
|
32
|
+
const created = await facade.createCase({ ...identity, idempotencyKey: 'create', projectRoot: root, baseRef: 'HEAD', targetRef: 'main', title: 'composition' });
|
|
33
|
+
const answered = await facade.answer(created.caseId, { ...identity, idempotencyKey: 'answer', turn: 1, value: 'criteria' });
|
|
34
|
+
expect(answered.stage).toBe('clarifying');
|
|
35
|
+
const stored = await facade.status(created.caseId);
|
|
36
|
+
expect(stored.caseId).toBe(created.caseId);
|
|
37
|
+
const approved = await facade.approve(created.caseId, { ...identity, idempotencyKey: 'intent-compose', turn: 1, stage: 'intent' });
|
|
38
|
+
expect(approved.stage).toBe('designing');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=persistence.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.test.js","sourceRoot":"","sources":["../../src/__tests__/persistence.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-spike.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/recovery-spike.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { cloneRecoverySnapshot, RecoveryCoordinator } from '../recovery-spike.js';
|
|
3
|
+
describe('delivery line recovery spike', () => {
|
|
4
|
+
it('resumes an unfinished task once after process reconstruction', async () => {
|
|
5
|
+
const calls = [];
|
|
6
|
+
const initial = {
|
|
7
|
+
caseId: 'case-1', stage: 'executing',
|
|
8
|
+
tasks: [{ taskId: 'task-1', status: 'pending', attempt: 0, idempotencyKey: 'case-1:task-1:1' }],
|
|
9
|
+
};
|
|
10
|
+
const first = new RecoveryCoordinator(cloneRecoverySnapshot(initial), { startTask: async (id) => { calls.push(id); } });
|
|
11
|
+
const recovered = await first.resume();
|
|
12
|
+
expect(recovered.tasks[0]).toMatchObject({ status: 'running', attempt: 1 });
|
|
13
|
+
expect(calls).toEqual(['task-1']);
|
|
14
|
+
const second = new RecoveryCoordinator(cloneRecoverySnapshot(recovered), { startTask: async (id) => { calls.push(id); } });
|
|
15
|
+
const resumed = await second.resume();
|
|
16
|
+
expect(resumed.stage).toBe('held');
|
|
17
|
+
expect(calls).toEqual(['task-1']);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=recovery-spike.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-spike.test.js","sourceRoot":"","sources":["../../src/__tests__/recovery-spike.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAyB,MAAM,sBAAsB,CAAC;AAEzG,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAqB;YAChC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW;YACpC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC;SAChG,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxH,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;QACvC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAElC,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,qBAAqB,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3H,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-recovery.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sqlite-recovery.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { SQLiteCaseStore } from '../persistence/sqlite-store.js';
|
|
6
|
+
import { DeliveryLineFacade } from '../facade/delivery-line-facade.js';
|
|
7
|
+
const makeCase = (overrides = {}) => ({
|
|
8
|
+
id: 'recovery-case', projectRoot: '/repo', baseRef: 'main', integrationRef: 'refs/devflow/delivery/recovery-case', targetRef: 'main', title: 'recovery', source: 'human', riskTier: 'low', autonomy: 'manual', stage: 'executing',
|
|
9
|
+
budgets: { maxInteractionTurns: 4, maxTaskAttempts: 2, maxWallTimeMs: 1000, maxToolCalls: 10 }, artifacts: [], tasks: [], approvals: [], decisions: [], evidence: [],
|
|
10
|
+
interaction: { phase: 'execution', status: 'awaiting_user', turn: 1, openQuestions: ['choose'], blockingUnknowns: ['choose'], summaryRefs: [], summaries: [{ id: 'summary-1', phase: 'execution', turn: 1, question: 'choose', actor: { id: 'user', kind: 'human' } }] },
|
|
11
|
+
createdBy: { id: 'user', kind: 'human' }, updatedAt: 10, version: 0, ...overrides,
|
|
12
|
+
});
|
|
13
|
+
describe('SQLite recovery contract', () => {
|
|
14
|
+
it('creates every required table and column', () => {
|
|
15
|
+
const store = new SQLiteCaseStore(join(mkdtempSync(join(tmpdir(), 'delivery-line-schema-')), 'state.db'));
|
|
16
|
+
const required = {
|
|
17
|
+
delivery_cases: ['id', 'payload', 'version', 'updated_at'],
|
|
18
|
+
delivery_tasks: ['id', 'case_id', 'payload', 'version', 'lease_owner', 'lease_expires_at', 'attempt'],
|
|
19
|
+
delivery_artifacts: ['id', 'case_id', 'payload', 'content_hash'],
|
|
20
|
+
delivery_approvals: ['id', 'case_id', 'payload'],
|
|
21
|
+
delivery_evidence: ['id', 'case_id', 'payload', 'evidence_hash'],
|
|
22
|
+
delivery_events: ['id', 'case_id', 'idempotency_key', 'actor', 'type', 'payload', 'created_at', 'processed_at'],
|
|
23
|
+
delivery_interactions: ['case_id', 'phase', 'turn', 'prompt_or_summary', 'answer', 'decision', 'blocking', 'artifact_hash'],
|
|
24
|
+
delivery_subagent_sessions: ['session_id', 'case_id', 'role', 'agent_id', 'agent_version', 'permission_snapshot', 'status', 'last_event_id'],
|
|
25
|
+
delivery_handoffs: ['case_id', 'idempotency_key', 'source_role', 'target_role', 'input_refs', 'output_refs', 'context_receipt', 'status', 'payload', 'processed_at'],
|
|
26
|
+
};
|
|
27
|
+
for (const [table, columns] of Object.entries(required)) {
|
|
28
|
+
const actual = new Set(store.db.prepare(`PRAGMA table_info(${table})`).all().map(column => column.name));
|
|
29
|
+
for (const column of columns)
|
|
30
|
+
expect(actual.has(column), `${table}.${column}`).toBe(true);
|
|
31
|
+
}
|
|
32
|
+
store.close();
|
|
33
|
+
});
|
|
34
|
+
it('recovers pending work, event cursor and hand-off state after reopening', async () => {
|
|
35
|
+
const path = join(mkdtempSync(join(tmpdir(), 'delivery-line-recovery-')), 'state.db');
|
|
36
|
+
const first = new SQLiteCaseStore(path);
|
|
37
|
+
const record = makeCase({ tasks: [{ id: 'task-1', caseId: 'recovery-case', title: 'task', attempt: 1, version: 0, baseRevision: 'main', dependsOn: [], allowedFiles: [], expectedChanges: [], verificationCommands: [], status: 'verified', evidence: [] }] });
|
|
38
|
+
await first.create(record);
|
|
39
|
+
const event = { id: 'event-1', caseId: record.id, type: 'user.answer', idempotencyKey: 'answer-1', actor: record.createdBy, payload: { answer: 'yes' }, createdAt: 20 };
|
|
40
|
+
await first.appendEvent(event);
|
|
41
|
+
const handoff = { caseId: record.id, from: { role: 'designer', agentId: 'designer', version: 'v1', sessionId: 's1' }, to: { role: 'planner', agentId: 'planner', version: 'v1' }, inputRefs: ['intent'], contextReceipt: 'ctx-1', claims: [{ statement: 'ok', evidenceRefs: ['e1'] }], outputRefs: ['plan'], status: 'complete', idempotencyKey: 'handoff-1' };
|
|
42
|
+
first.recordHandoff(handoff, 21);
|
|
43
|
+
expect(first.acquireTaskLease({ taskId: 'task-1', caseId: record.id, owner: 'worker-a', now: 30, leaseMs: 10, payload: JSON.stringify(record.tasks[0]) })).toBe(true);
|
|
44
|
+
first.close();
|
|
45
|
+
const recovered = new SQLiteCaseStore(path);
|
|
46
|
+
expect((await recovered.get(record.id)).tasks[0].status).toBe('verified');
|
|
47
|
+
expect((await recovered.listUnprocessedEvents(record.id)).map(item => item.idempotencyKey)).toEqual(['answer-1']);
|
|
48
|
+
expect(recovered.latestEventCursor(record.id)?.id).toBe('event-1');
|
|
49
|
+
expect(recovered.listPendingInteractions(record.id)).toHaveLength(1);
|
|
50
|
+
expect(recovered.listUnprocessedHandoffs(record.id)).toHaveLength(1);
|
|
51
|
+
expect(recovered.listVerifiedNotIntegratedTasks(record.id).map(task => task.id)).toEqual(['task-1']);
|
|
52
|
+
expect(recovered.listActiveLeases(35, record.id)).toHaveLength(1);
|
|
53
|
+
expect(await recovered.markEventProcessed('answer-1', 40)).toBe(true);
|
|
54
|
+
expect(await recovered.markEventProcessed('answer-1', 41)).toBe(false);
|
|
55
|
+
expect(recovered.markHandoffProcessed('handoff-1', 42)).toBe(true);
|
|
56
|
+
expect(recovered.listUnprocessedHandoffs(record.id)).toHaveLength(0);
|
|
57
|
+
recovered.close();
|
|
58
|
+
});
|
|
59
|
+
it('fences concurrent lease takeover and records it in the ledger', () => {
|
|
60
|
+
const store = new SQLiteCaseStore(join(mkdtempSync(join(tmpdir(), 'delivery-line-lease-')), 'state.db'));
|
|
61
|
+
expect(store.acquireTaskLease({ taskId: 'task-1', caseId: 'case-1', owner: 'a', now: 1, leaseMs: 100 })).toBe(true);
|
|
62
|
+
expect(store.acquireTaskLease({ taskId: 'task-1', caseId: 'case-1', owner: 'b', now: 2, leaseMs: 100 })).toBe(false);
|
|
63
|
+
expect(store.acquireTaskLease({ taskId: 'task-1', caseId: 'case-1', owner: 'b', now: 101, leaseMs: 100 })).toBe(true);
|
|
64
|
+
expect(store.latestEventCursor('case-1')?.id).toContain('lease-takeover:task-1:b:101');
|
|
65
|
+
store.close();
|
|
66
|
+
});
|
|
67
|
+
it('persists explicit subagent cursor and processed user events', async () => {
|
|
68
|
+
const store = new SQLiteCaseStore(join(mkdtempSync(join(tmpdir(), 'delivery-line-session-')), 'state.db'));
|
|
69
|
+
const session = { sessionId: 's1', caseId: 'case-1', role: 'designer', agentId: 'delivery-designer', agentVersion: 'v1', permissionSnapshot: { readArtifacts: true }, status: 'running', lastEventId: 'e1', processedUserEventIds: ['u1'], lastUserTurn: 2, budget: { toolCalls: 1 }, createdAt: 1 };
|
|
70
|
+
await store.saveSubAgentSession(session);
|
|
71
|
+
expect(await store.getSubAgentSession('s1')).toMatchObject({ lastEventId: 'e1', processedUserEventIds: ['u1'], lastUserTurn: 2 });
|
|
72
|
+
store.close();
|
|
73
|
+
});
|
|
74
|
+
it('reconstructs the facade after restart without replaying a running task', async () => {
|
|
75
|
+
const path = join(mkdtempSync(join(tmpdir(), 'delivery-line-facade-restart-')), 'state.db');
|
|
76
|
+
const firstStore = new SQLiteCaseStore(path);
|
|
77
|
+
const record = makeCase({ contextReceipt: 'ctx-restart', tasks: [{ id: 'task-1', caseId: 'recovery-case', title: 'task', attempt: 1, version: 0, baseRevision: 'main', dependsOn: [], allowedFiles: [], expectedChanges: [], verificationCommands: [], status: 'running', evidence: [] }] });
|
|
78
|
+
await firstStore.create(record);
|
|
79
|
+
firstStore.close();
|
|
80
|
+
let starts = 0;
|
|
81
|
+
const secondStore = new SQLiteCaseStore(path);
|
|
82
|
+
const facade = new DeliveryLineFacade({ store: secondStore, execution: { startTask: async () => { starts += 1; return { taskId: 'task-1', status: 'started' }; }, cancelTask: async () => { } } });
|
|
83
|
+
const recovered = await facade.resume(record.id, { actor: record.createdBy, sessionId: 's', executionId: 'e', idempotencyKey: 'resume-restart', contextReceipt: 'ctx-restart' });
|
|
84
|
+
expect(recovered.stage).toBe('held');
|
|
85
|
+
expect(recovered.taskSummaries[0]).toMatchObject({ status: 'running', attempt: 1 });
|
|
86
|
+
expect(starts).toBe(0);
|
|
87
|
+
secondStore.close();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=sqlite-recovery.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-recovery.test.js","sourceRoot":"","sources":["../../src/__tests__/sqlite-recovery.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/state-machine.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { cancelCase, canAdvance, invalidateDownstream, transitionCase } from '../domain/state-machine.js';
|
|
3
|
+
import { DeliveryLineError } from '../domain/types.js';
|
|
4
|
+
function record(stage) {
|
|
5
|
+
return { id: 'case', projectRoot: '/repo', baseRef: 'main', integrationRef: 'refs/devflow/case', targetRef: 'main', title: 'change', source: 'human', riskTier: 'low', autonomy: 'manual', stage, budgets: { maxInteractionTurns: 4, maxTaskAttempts: 2, maxWallTimeMs: 1000, maxToolCalls: 10 }, artifacts: [], tasks: [], approvals: [], decisions: [], evidence: [], interaction: { phase: 'clarification', status: 'active', turn: 0, openQuestions: [], blockingUnknowns: [], summaryRefs: [] }, createdBy: { id: 'u', kind: 'human' }, updatedAt: 1, version: 0 };
|
|
6
|
+
}
|
|
7
|
+
describe('delivery state machine', () => {
|
|
8
|
+
it('supports the complete linear happy-path transition sequence', () => {
|
|
9
|
+
const facts = [
|
|
10
|
+
{}, { intentApproved: true }, { designStarted: true }, { specHash: 'spec' }, { specApproved: true },
|
|
11
|
+
{ planHash: 'plan' }, { planApproved: true }, { executableTask: true }, { allTasksIntegrated: true, planVerificationFresh: true },
|
|
12
|
+
{ reviewsPassed: true }, { releaseApproved: true }, { packageExported: true },
|
|
13
|
+
];
|
|
14
|
+
const stages = ['draft', 'clarifying', 'intent_approved', 'designing', 'spec_ready', 'spec_approved', 'plan_ready', 'plan_approved', 'executing', 'verified', 'reviewed', 'release_pending', 'delivered'];
|
|
15
|
+
let current = record(stages[0]);
|
|
16
|
+
for (let index = 0; index < facts.length; index += 1) {
|
|
17
|
+
current = transitionCase(current, facts[index]);
|
|
18
|
+
expect(current.stage).toBe(stages[index + 1]);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
it('advances only with current artifacts and evidence', () => {
|
|
22
|
+
expect(canAdvance('clarifying', { intentApproved: true, blockingUnknowns: 1 })).toBe(false);
|
|
23
|
+
const current = record('executing');
|
|
24
|
+
expect(() => transitionCase(current, { allTasksIntegrated: true, planVerificationFresh: true, currentArtifactHash: 'old', requiredArtifactHash: 'new' })).toThrow(DeliveryLineError);
|
|
25
|
+
const next = transitionCase(current, { allTasksIntegrated: true, planVerificationFresh: true, currentArtifactHash: 'h', requiredArtifactHash: 'h', requiredEvidenceRefs: ['verification'], evidenceRefs: ['verification'] });
|
|
26
|
+
expect(next.stage).toBe('verified');
|
|
27
|
+
expect(next.version).toBe(1);
|
|
28
|
+
});
|
|
29
|
+
it('treats cancellation as terminal and rejects invalid terminal transitions', () => {
|
|
30
|
+
const cancelled = cancelCase(record('executing'));
|
|
31
|
+
expect(cancelled.stage).toBe('cancelled');
|
|
32
|
+
expect(() => cancelCase(cancelled)).toThrow('cannot be cancelled');
|
|
33
|
+
expect(() => transitionCase(cancelled, {})).toThrow(DeliveryLineError);
|
|
34
|
+
});
|
|
35
|
+
it('invalidates downstream approvals, task integration and evidence after scope revision', () => {
|
|
36
|
+
const current = record('reviewed');
|
|
37
|
+
current.approvals = [
|
|
38
|
+
{ id: 'intent', stage: 'intent', actor: { id: 'u', kind: 'human' }, policyVersion: '1', artifactHash: 'i', decision: 'approved', reason: 'ok', createdAt: 1 },
|
|
39
|
+
{ id: 'spec', stage: 'spec', actor: { id: 'u', kind: 'human' }, policyVersion: '1', artifactHash: 's', decision: 'approved', reason: 'ok', createdAt: 1 },
|
|
40
|
+
];
|
|
41
|
+
current.tasks = [{ id: 't', caseId: 'case', title: 'task', attempt: 1, version: 1, baseRevision: 'main', dependsOn: [], allowedFiles: [], expectedChanges: [], verificationCommands: [], status: 'integrated', integratedRevision: 'candidate', evidence: [] }];
|
|
42
|
+
current.evidence = [{ id: 'e', kind: 'verification', hash: 'h', createdAt: 1, metadata: {} }];
|
|
43
|
+
const revised = invalidateDownstream('clarifying', current);
|
|
44
|
+
expect(revised.approvals).toEqual([]);
|
|
45
|
+
expect(revised.tasks[0]).toMatchObject({ status: 'pending', integratedRevision: undefined });
|
|
46
|
+
expect(revised.evidence).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=state-machine.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.test.js","sourceRoot":"","sources":["../../src/__tests__/state-machine.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-orchestrator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/subagent-orchestrator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { MemoryCaseStore } from '../persistence/memory-store.js';
|
|
3
|
+
import { SubAgentOrchestrator } from '../subagents/orchestrator.js';
|
|
4
|
+
describe('SubAgent orchestrator contract', () => {
|
|
5
|
+
it('uses fixed role identity, persists session cursor and recovers without creating an agent', async () => {
|
|
6
|
+
const calls = [];
|
|
7
|
+
const port = {
|
|
8
|
+
start: async () => { calls.push('start'); },
|
|
9
|
+
send: async (value) => value,
|
|
10
|
+
interrupt: async () => { calls.push('interrupt'); },
|
|
11
|
+
recover: async () => { calls.push('recover'); return { sessionId: 's1', caseId: 'case', role: 'designer', agentId: 'delivery-designer', agentVersion: 'v1', permissionSnapshot: { readArtifacts: true }, status: 'running', processedUserEventIds: ['u1'], lastUserTurn: 1, budget: { toolCalls: 1 }, createdAt: 1 }; },
|
|
12
|
+
};
|
|
13
|
+
const store = new MemoryCaseStore();
|
|
14
|
+
const orchestrator = new SubAgentOrchestrator(port, store);
|
|
15
|
+
const session = await orchestrator.startSession({ caseId: 'case', role: 'designer', sessionId: 's1' });
|
|
16
|
+
expect(session.agentId).toBe('delivery-designer');
|
|
17
|
+
orchestrator.recordUserEvent('s1', { eventId: 'u1', turn: 1 });
|
|
18
|
+
expect(() => orchestrator.recordUserEvent('s1', { eventId: 'u1', turn: 1 })).toThrow('already');
|
|
19
|
+
orchestrator.recordEvent('s1', { id: 'e1', toolCall: true });
|
|
20
|
+
expect((await store.getSubAgentSession('s1')).lastEventId).toBe('e1');
|
|
21
|
+
await orchestrator.recoverSession('s1');
|
|
22
|
+
expect(calls).toEqual(['start', 'recover']);
|
|
23
|
+
});
|
|
24
|
+
it('deduplicates the same hand-off key and rejects a changed payload', async () => {
|
|
25
|
+
const port = { start: async () => undefined, send: async (value) => value, interrupt: async () => undefined, recover: async () => undefined };
|
|
26
|
+
const orchestrator = new SubAgentOrchestrator(port);
|
|
27
|
+
const handoff = { caseId: 'case', from: { role: 'designer', agentId: 'delivery-designer', version: 'v1', sessionId: 's1' }, to: { role: 'planner', agentId: 'delivery-planner', version: 'v1' }, inputRefs: ['spec'], contextReceipt: 'ctx', claims: [{ statement: 'ok', evidenceRefs: ['e1'] }], outputRefs: ['plan'], status: 'complete', idempotencyKey: 'h1' };
|
|
28
|
+
await orchestrator.handoff(handoff, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec'] });
|
|
29
|
+
await expect(orchestrator.handoff(handoff, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec'] })).resolves.toMatchObject({ status: 'duplicate' });
|
|
30
|
+
await expect(orchestrator.handoff({ ...handoff, outputRefs: ['other'] }, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec'] })).rejects.toMatchObject({ code: 'IDEMPOTENCY_CONFLICT' });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=subagent-orchestrator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-orchestrator.test.js","sourceRoot":"","sources":["../../src/__tests__/subagent-orchestrator.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-recovery.e2e.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/subagent-recovery.e2e.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { consumeClaudeSession, createClaudeSessionState, mapClaudeEvent, shouldBreak } from '../subagents/claude-events.js';
|
|
3
|
+
import { validateAgentHandoff } from '../subagents/handoff-validator.js';
|
|
4
|
+
import { MemoryCaseStore } from '../persistence/memory-store.js';
|
|
5
|
+
import { SubAgentOrchestrator } from '../subagents/orchestrator.js';
|
|
6
|
+
describe('subagent recovery journey', () => {
|
|
7
|
+
it('replays history once and keeps tool confirmation non-terminal', () => {
|
|
8
|
+
const state = createClaudeSessionState();
|
|
9
|
+
const event = { id: 'tool-1', type: 'agent.tool_use', name: 'confirm', input: { value: 'yes' } };
|
|
10
|
+
expect(mapClaudeEvent(event, state)).toMatchObject({ type: 'tool_confirmation_required' });
|
|
11
|
+
expect(mapClaudeEvent(event, state)).toBeUndefined();
|
|
12
|
+
const waiting = mapClaudeEvent({ id: 'idle-1', type: 'session.status_idle', stop_reason: { type: 'requires_action' } }, state);
|
|
13
|
+
expect(shouldBreak(waiting)).toBe(false);
|
|
14
|
+
const handoff = { caseId: 'case', from: { role: 'designer', agentId: 'designer', version: 'v1', sessionId: 'session' }, to: { role: 'planner', agentId: 'planner', version: 'v1' }, inputRefs: ['spec'], contextReceipt: 'ctx', claims: [{ statement: 'scope preserved', evidenceRefs: ['evidence'] }], outputRefs: ['plan'], status: 'complete', idempotencyKey: 'handoff-1' };
|
|
15
|
+
expect(() => validateAgentHandoff(handoff, { caseId: 'case', contextReceipt: 'ctx', inputRefs: ['spec'] })).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
it('recovers a dropped stream without duplicating user turn or hand-off', async () => {
|
|
18
|
+
const state = createClaudeSessionState();
|
|
19
|
+
const stream = async function* () { yield { id: 'tool-2', type: 'agent.tool_use', name: 'confirm', input: {} }; };
|
|
20
|
+
const events = await consumeClaudeSession({ openStream: async () => stream(), kickoff: async () => { }, history: async () => [{ id: 'tool-2', type: 'agent.tool_use', name: 'confirm', input: {} }, { id: 'compact-1', type: 'compaction', marker: 'compact-1' }] }, state);
|
|
21
|
+
expect(events.map(event => event.type)).toEqual(['tool_confirmation_required', 'compaction']);
|
|
22
|
+
const store = new MemoryCaseStore();
|
|
23
|
+
const calls = [];
|
|
24
|
+
const port = { start: async () => { calls.push('start'); }, send: async (value) => value, interrupt: async () => { }, recover: async () => { calls.push('recover'); return store.getSubAgentSession('session-1'); } };
|
|
25
|
+
const first = new SubAgentOrchestrator(port, store);
|
|
26
|
+
await first.startSession({ caseId: 'case', role: 'designer', sessionId: 'session-1' });
|
|
27
|
+
first.recordUserEvent('session-1', { eventId: 'user-1', turn: 1 });
|
|
28
|
+
const restarted = new SubAgentOrchestrator(port, store);
|
|
29
|
+
const recovered = await restarted.recoverSession('session-1');
|
|
30
|
+
expect(recovered.processedUserEventIds).toEqual(['user-1']);
|
|
31
|
+
expect(() => restarted.recordUserEvent('session-1', { eventId: 'user-1', turn: 1 })).toThrow('already');
|
|
32
|
+
expect(calls).toEqual(['start', 'recover']);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=subagent-recovery.e2e.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-recovery.e2e.test.js","sourceRoot":"","sources":["../../src/__tests__/subagent-recovery.e2e.test.ts"],"names":[],"mappings":"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"}
|