@dharma-ai-labs/agent-fabric 0.2.212 → 0.2.215

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.
@@ -1639,6 +1639,72 @@ test('oversized queued current-device revision is retired only after checking th
1639
1639
  await assert.rejects(() => syncPendingRetentionCapsules(vault, fabric, policyWithSmallerCap, 'workspace-northstar'), /conflicts with the accepted server head/);
1640
1640
  assert.equal(pending.length, 1);
1641
1641
  });
1642
+ test('queued capsule whose time range ends before it starts is retired and does not block the queue', async () => {
1643
+ const base = await materializeWorkspacePolicy({
1644
+ workspace: await mkdtemp(join(tmpdir(), 'dharma-inverted-capsule-')),
1645
+ organizationId: 'org_northstar', revision: 'local',
1646
+ });
1647
+ const deviceId = '22222222-2222-4222-8222-222222222222';
1648
+ const session = (sessionId, startedAt, endedAt) => ({
1649
+ provider: 'claude', sessionId, sourcePath: '/private/claude.jsonl',
1650
+ workspace: '/repo', coverage: 'observed', startedAt, endedAt,
1651
+ records: [{ native: { type: 'system' }, sourcePath: '/private/claude.jsonl', line: 1,
1652
+ workspace: '/repo', timestamp: endedAt, kind: 'metadata' }],
1653
+ });
1654
+ // CLI 0.2.212 queued this shape: start from the file birthtime, end from an earlier record.
1655
+ const inverted = buildTrajectoryCapsule({
1656
+ organizationId: 'org_northstar', deviceId, workspaceId: 'workspace-northstar',
1657
+ session: session('login-only', '2026-10-31T03:19:40.946Z', '2026-10-31T03:19:40.909Z'),
1658
+ policy: base.policy, rawContentId: `sha256:${'a'.repeat(64)}`, rawBytes: 32,
1659
+ });
1660
+ const valid = buildTrajectoryCapsule({
1661
+ organizationId: 'org_northstar', deviceId, workspaceId: 'workspace-northstar',
1662
+ session: session('later-work', '2026-10-31T03:21:27.212Z', '2026-10-31T03:22:55.503Z'),
1663
+ policy: base.policy, rawContentId: `sha256:${'b'.repeat(64)}`, rawBytes: 32,
1664
+ });
1665
+ const pending = [inverted, valid];
1666
+ const discarded = [];
1667
+ const sent = [];
1668
+ const headsRequested = [];
1669
+ const vault = {
1670
+ listPendingCapsuleSyncs: async () => pending.map(capsule => ({ trajectoryId: capsule.trajectoryId,
1671
+ revision: capsule.revision, capsule })),
1672
+ markCapsuleSynced: (trajectoryId) => { pending.splice(pending.findIndex(c => c.trajectoryId === trajectoryId), 1); },
1673
+ discardPendingCapsuleSync: (trajectoryId, _revision, reason) => {
1674
+ discarded.push({ trajectoryId, reason });
1675
+ pending.splice(pending.findIndex(c => c.trajectoryId === trajectoryId), 1);
1676
+ },
1677
+ };
1678
+ const fabric = {
1679
+ config: { organizationId: 'org_northstar', deviceId },
1680
+ getTrajectoryHead: async ({ trajectoryId }) => {
1681
+ headsRequested.push(trajectoryId);
1682
+ return { trajectoryId, head: null };
1683
+ },
1684
+ syncTrajectory: async (capsule) => {
1685
+ if (Date.parse(capsule.timeRange.end) < Date.parse(capsule.timeRange.start)) {
1686
+ throw Object.assign(new Error('Trajectory end precedes its start.'), { code: 'invalid_trajectory_capsule', status: 400 });
1687
+ }
1688
+ sent.push(capsule.trajectoryId);
1689
+ },
1690
+ };
1691
+ const stderr = [];
1692
+ const write = process.stderr.write;
1693
+ process.stderr.write = ((chunk) => { stderr.push(String(chunk)); return true; });
1694
+ let count;
1695
+ try {
1696
+ count = await syncPendingRetentionCapsules(vault, fabric, base.policy, 'workspace-northstar');
1697
+ }
1698
+ finally {
1699
+ process.stderr.write = write;
1700
+ }
1701
+ assert.equal(count, 1);
1702
+ assert.deepEqual(discarded, [{ trajectoryId: inverted.trajectoryId, reason: 'time_range_inverted_superseded' }]);
1703
+ assert.deepEqual(sent, [valid.trajectoryId]);
1704
+ assert.deepEqual(headsRequested, [inverted.trajectoryId]);
1705
+ assert.equal(pending.length, 0);
1706
+ assert.match(stderr.join(''), /time range ends before it starts was retired/);
1707
+ });
1642
1708
  test('queued native recipient paths retire only an unsent revision without uploading it', async () => {
1643
1709
  const workspace = await mkdtemp(join(tmpdir(), 'dharma-recipient-path-capsule-'));
1644
1710
  const signed = signedPolicyAuthorization({