gsd-pi 2.45.0 → 2.46.0
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/dist/help-text.js +1 -1
- package/dist/loader.js +34 -0
- package/dist/resources/extensions/gsd/auto/phases.js +27 -42
- package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
- package/dist/resources/extensions/gsd/auto/session.js +0 -11
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
- package/dist/resources/extensions/gsd/auto.js +12 -57
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
- package/dist/resources/extensions/gsd/commands/context.js +0 -4
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
- package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
- package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
- package/dist/resources/extensions/gsd/db-writer.js +9 -9
- package/dist/resources/extensions/gsd/doctor-checks.js +167 -2
- package/dist/resources/extensions/gsd/doctor.js +5 -3
- package/dist/resources/extensions/gsd/gsd-db.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +1 -2
- package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
- package/dist/resources/extensions/gsd/preferences-types.js +2 -2
- package/dist/resources/extensions/gsd/preferences.js +8 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
- package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/session-lock.js +1 -3
- package/dist/resources/extensions/gsd/state.js +7 -0
- package/dist/resources/extensions/gsd/sync-lock.js +89 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +61 -11
- package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
- package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
- package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
- package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
- package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
- package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
- package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
- package/dist/resources/extensions/gsd/workflow-events.js +102 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +193 -0
- package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
- package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
- package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
- package/dist/resources/extensions/gsd/write-intercept.js +84 -0
- package/dist/resources/extensions/voice/index.js +11 -16
- package/dist/resources/extensions/voice/linux-ready.js +67 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
- package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
- package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
- package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
- package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
- package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
- package/src/resources/extensions/gsd/auto/phases.ts +24 -44
- package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
- package/src/resources/extensions/gsd/auto/session.ts +0 -18
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
- package/src/resources/extensions/gsd/auto-start.ts +1 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
- package/src/resources/extensions/gsd/auto.ts +7 -83
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
- package/src/resources/extensions/gsd/commands/context.ts +0 -5
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
- package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
- package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
- package/src/resources/extensions/gsd/db-writer.ts +9 -17
- package/src/resources/extensions/gsd/doctor-checks.ts +180 -2
- package/src/resources/extensions/gsd/doctor-types.ts +7 -1
- package/src/resources/extensions/gsd/doctor.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +16 -3
- package/src/resources/extensions/gsd/guided-flow.ts +1 -2
- package/src/resources/extensions/gsd/journal.ts +6 -1
- package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
- package/src/resources/extensions/gsd/preferences-types.ts +2 -2
- package/src/resources/extensions/gsd/preferences.ts +7 -3
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +21 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
- package/src/resources/extensions/gsd/prompts/queue.md +2 -2
- package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/session-lock.ts +0 -4
- package/src/resources/extensions/gsd/state.ts +8 -0
- package/src/resources/extensions/gsd/sync-lock.ts +94 -0
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
- package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
- package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +74 -11
- package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
- package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
- package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
- package/src/resources/extensions/gsd/workflow-events.ts +154 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +243 -0
- package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
- package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
- package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
- package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
- package/src/resources/extensions/gsd/write-intercept.ts +90 -0
- package/src/resources/extensions/voice/index.ts +11 -21
- package/src/resources/extensions/voice/linux-ready.ts +87 -0
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
- /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → 8zT99piZz8u3xAU3Omz2g}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{wUzEX1U3CmFcMry2SUDJn → 8zT99piZz8u3xAU3Omz2g}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// GSD — reopen-task handler tests
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
openDatabase,
|
|
12
|
+
closeDatabase,
|
|
13
|
+
insertMilestone,
|
|
14
|
+
insertSlice,
|
|
15
|
+
insertTask,
|
|
16
|
+
getTask,
|
|
17
|
+
} from '../gsd-db.ts';
|
|
18
|
+
import { handleReopenTask } from '../tools/reopen-task.ts';
|
|
19
|
+
|
|
20
|
+
function makeTmpBase(): string {
|
|
21
|
+
const base = mkdtempSync(join(tmpdir(), 'gsd-reopen-task-'));
|
|
22
|
+
mkdirSync(join(base, '.gsd', 'milestones', 'M001', 'slices', 'S01', 'tasks'), { recursive: true });
|
|
23
|
+
return base;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function cleanup(base: string): void {
|
|
27
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
28
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function seedCompleteTask(): void {
|
|
32
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone', status: 'active' });
|
|
33
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Test Slice', status: 'in_progress' });
|
|
34
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', title: 'Task One', status: 'complete' });
|
|
35
|
+
insertTask({ id: 'T02', sliceId: 'S01', milestoneId: 'M001', title: 'Task Two', status: 'pending' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Success path ────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
test('handleReopenTask: resets a complete task to pending', async () => {
|
|
41
|
+
const base = makeTmpBase();
|
|
42
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
43
|
+
try {
|
|
44
|
+
seedCompleteTask();
|
|
45
|
+
|
|
46
|
+
const result = await handleReopenTask({
|
|
47
|
+
milestoneId: 'M001',
|
|
48
|
+
sliceId: 'S01',
|
|
49
|
+
taskId: 'T01',
|
|
50
|
+
reason: 'verification failed after merge',
|
|
51
|
+
}, base);
|
|
52
|
+
|
|
53
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
54
|
+
assert.equal(result.taskId, 'T01');
|
|
55
|
+
|
|
56
|
+
const task = getTask('M001', 'S01', 'T01');
|
|
57
|
+
assert.ok(task, 'task should still exist');
|
|
58
|
+
assert.equal(task!.status, 'pending', 'task status should be reset to pending');
|
|
59
|
+
} finally {
|
|
60
|
+
cleanup(base);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('handleReopenTask: does not affect other tasks in the slice', async () => {
|
|
65
|
+
const base = makeTmpBase();
|
|
66
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
67
|
+
try {
|
|
68
|
+
seedCompleteTask();
|
|
69
|
+
|
|
70
|
+
await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
|
|
71
|
+
|
|
72
|
+
const t02 = getTask('M001', 'S01', 'T02');
|
|
73
|
+
assert.ok(t02, 'T02 should still exist');
|
|
74
|
+
assert.equal(t02!.status, 'pending', 'T02 status should be unchanged');
|
|
75
|
+
} finally {
|
|
76
|
+
cleanup(base);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ─── Failure paths ───────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
test('handleReopenTask: rejects empty taskId', async () => {
|
|
83
|
+
const base = makeTmpBase();
|
|
84
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
85
|
+
try {
|
|
86
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: '' }, base);
|
|
87
|
+
assert.ok('error' in result);
|
|
88
|
+
assert.match(result.error, /taskId/);
|
|
89
|
+
} finally {
|
|
90
|
+
cleanup(base);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('handleReopenTask: rejects non-existent milestone', async () => {
|
|
95
|
+
const base = makeTmpBase();
|
|
96
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
97
|
+
try {
|
|
98
|
+
const result = await handleReopenTask({ milestoneId: 'M999', sliceId: 'S01', taskId: 'T01' }, base);
|
|
99
|
+
assert.ok('error' in result);
|
|
100
|
+
assert.match(result.error, /milestone not found/);
|
|
101
|
+
} finally {
|
|
102
|
+
cleanup(base);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('handleReopenTask: rejects task in a closed milestone', async () => {
|
|
107
|
+
const base = makeTmpBase();
|
|
108
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
109
|
+
try {
|
|
110
|
+
insertMilestone({ id: 'M001', title: 'Done', status: 'complete' });
|
|
111
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
112
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
113
|
+
|
|
114
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
|
|
115
|
+
assert.ok('error' in result);
|
|
116
|
+
assert.match(result.error, /closed milestone/);
|
|
117
|
+
} finally {
|
|
118
|
+
cleanup(base);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('handleReopenTask: rejects task inside a closed slice', async () => {
|
|
123
|
+
const base = makeTmpBase();
|
|
124
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
125
|
+
try {
|
|
126
|
+
insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
|
|
127
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
128
|
+
insertTask({ id: 'T01', sliceId: 'S01', milestoneId: 'M001', status: 'complete' });
|
|
129
|
+
|
|
130
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T01' }, base);
|
|
131
|
+
assert.ok('error' in result);
|
|
132
|
+
assert.match(result.error, /closed slice/);
|
|
133
|
+
} finally {
|
|
134
|
+
cleanup(base);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('handleReopenTask: rejects reopening a task that is not complete', async () => {
|
|
139
|
+
const base = makeTmpBase();
|
|
140
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
141
|
+
try {
|
|
142
|
+
seedCompleteTask();
|
|
143
|
+
|
|
144
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T02' }, base);
|
|
145
|
+
assert.ok('error' in result);
|
|
146
|
+
assert.match(result.error, /not complete/);
|
|
147
|
+
} finally {
|
|
148
|
+
cleanup(base);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('handleReopenTask: rejects non-existent task', async () => {
|
|
153
|
+
const base = makeTmpBase();
|
|
154
|
+
openDatabase(join(base, '.gsd', 'gsd.db'));
|
|
155
|
+
try {
|
|
156
|
+
insertMilestone({ id: 'M001', title: 'Active', status: 'active' });
|
|
157
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', status: 'in_progress' });
|
|
158
|
+
|
|
159
|
+
const result = await handleReopenTask({ milestoneId: 'M001', sliceId: 'S01', taskId: 'T99' }, base);
|
|
160
|
+
assert.ok('error' in result);
|
|
161
|
+
assert.match(result.error, /task not found/);
|
|
162
|
+
} finally {
|
|
163
|
+
cleanup(base);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
@@ -103,7 +103,7 @@ describe('session-lock-regression', async () => {
|
|
|
103
103
|
try {
|
|
104
104
|
acquireSessionLock(base);
|
|
105
105
|
|
|
106
|
-
updateSessionLock(base, 'execute-task', 'M001/S01/T01',
|
|
106
|
+
updateSessionLock(base, 'execute-task', 'M001/S01/T01', '/tmp/session.json');
|
|
107
107
|
|
|
108
108
|
const data = readSessionLockData(base);
|
|
109
109
|
assert.ok(data !== null, 'lock data readable after update');
|
|
@@ -111,7 +111,6 @@ describe('session-lock-regression', async () => {
|
|
|
111
111
|
assert.deepStrictEqual(data.pid, process.pid, 'lock data has correct PID');
|
|
112
112
|
assert.deepStrictEqual(data.unitType, 'execute-task', 'lock data has correct unit type');
|
|
113
113
|
assert.deepStrictEqual(data.unitId, 'M001/S01/T01', 'lock data has correct unit ID');
|
|
114
|
-
assert.deepStrictEqual(data.completedUnits, 5, 'lock data has correct completed count');
|
|
115
114
|
assert.deepStrictEqual(data.sessionFile, '/tmp/session.json', 'lock data has session file');
|
|
116
115
|
}
|
|
117
116
|
|
|
@@ -136,7 +135,6 @@ describe('session-lock-regression', async () => {
|
|
|
136
135
|
unitType: 'execute-task',
|
|
137
136
|
unitId: 'M001/S01/T01',
|
|
138
137
|
unitStartedAt: new Date(Date.now() - 3600000).toISOString(),
|
|
139
|
-
completedUnits: 3,
|
|
140
138
|
};
|
|
141
139
|
writeFileSync(lockFile, JSON.stringify(staleLock, null, 2));
|
|
142
140
|
|
|
@@ -233,7 +231,6 @@ describe('session-lock-regression', async () => {
|
|
|
233
231
|
unitType: 'execute-task',
|
|
234
232
|
unitId: 'M001/S01/T01',
|
|
235
233
|
unitStartedAt: new Date().toISOString(),
|
|
236
|
-
completedUnits: 0,
|
|
237
234
|
}, null, 2));
|
|
238
235
|
|
|
239
236
|
const status = getSessionLockStatus(base);
|
|
@@ -64,7 +64,7 @@ test("stopAutoRemote cleans up stale lock (dead PID) and returns found:false", (
|
|
|
64
64
|
const base = makeTmpBase();
|
|
65
65
|
try {
|
|
66
66
|
// Write a lock with a PID that doesn't exist
|
|
67
|
-
writeLock(base, "execute-task", "M001/S01/T01"
|
|
67
|
+
writeLock(base, "execute-task", "M001/S01/T01");
|
|
68
68
|
// Overwrite PID to a dead one
|
|
69
69
|
const lock = readCrashLock(base)!;
|
|
70
70
|
const staleData = { ...lock, pid: 999999999 };
|
|
@@ -111,7 +111,6 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", {
|
|
|
111
111
|
unitType: "execute-task",
|
|
112
112
|
unitId: "M001/S01/T01",
|
|
113
113
|
unitStartedAt: new Date().toISOString(),
|
|
114
|
-
completedUnits: 0,
|
|
115
114
|
};
|
|
116
115
|
writeFileSync(join(base, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2), "utf-8");
|
|
117
116
|
|
|
@@ -143,7 +142,7 @@ test("lock file should be discoverable at project root, not worktree path", () =
|
|
|
143
142
|
|
|
144
143
|
try {
|
|
145
144
|
// Simulate: auto-mode writes lock to project root (the fix)
|
|
146
|
-
writeLock(projectRoot, "execute-task", "M001/S01/T01"
|
|
145
|
+
writeLock(projectRoot, "execute-task", "M001/S01/T01");
|
|
147
146
|
|
|
148
147
|
// Second terminal checks project root — should find the lock
|
|
149
148
|
const lock = readCrashLock(projectRoot);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// GSD Extension — sync-lock unit tests
|
|
2
|
+
// Tests acquireSyncLock() and releaseSyncLock().
|
|
3
|
+
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
import { acquireSyncLock, releaseSyncLock } from '../sync-lock.ts';
|
|
10
|
+
|
|
11
|
+
function tempDir(): string {
|
|
12
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-sync-lock-'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function cleanupDir(dirPath: string): void {
|
|
16
|
+
try { fs.rmSync(dirPath, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ─── acquireSyncLock ─────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
test('sync-lock: acquireSyncLock returns { acquired: true } when no lock exists', () => {
|
|
22
|
+
const base = tempDir();
|
|
23
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
24
|
+
try {
|
|
25
|
+
const result = acquireSyncLock(base);
|
|
26
|
+
assert.strictEqual(result.acquired, true);
|
|
27
|
+
} finally {
|
|
28
|
+
cleanupDir(base);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('sync-lock: acquireSyncLock creates lock file at .gsd/sync.lock', () => {
|
|
33
|
+
const base = tempDir();
|
|
34
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
35
|
+
try {
|
|
36
|
+
acquireSyncLock(base);
|
|
37
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
38
|
+
assert.ok(fs.existsSync(lockPath), 'sync.lock should exist after acquire');
|
|
39
|
+
} finally {
|
|
40
|
+
cleanupDir(base);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('sync-lock: lock file contains pid and acquired_at fields', () => {
|
|
45
|
+
const base = tempDir();
|
|
46
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
47
|
+
try {
|
|
48
|
+
acquireSyncLock(base);
|
|
49
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
50
|
+
const content = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
|
|
51
|
+
assert.strictEqual(typeof content.pid, 'number');
|
|
52
|
+
assert.strictEqual(typeof content.acquired_at, 'string');
|
|
53
|
+
} finally {
|
|
54
|
+
cleanupDir(base);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ─── releaseSyncLock ─────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
test('sync-lock: releaseSyncLock removes lock file', () => {
|
|
61
|
+
const base = tempDir();
|
|
62
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
63
|
+
try {
|
|
64
|
+
acquireSyncLock(base);
|
|
65
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
66
|
+
assert.ok(fs.existsSync(lockPath), 'lock file should exist before release');
|
|
67
|
+
releaseSyncLock(base);
|
|
68
|
+
assert.ok(!fs.existsSync(lockPath), 'lock file should not exist after release');
|
|
69
|
+
} finally {
|
|
70
|
+
cleanupDir(base);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('sync-lock: releaseSyncLock is a no-op when no lock file exists', () => {
|
|
75
|
+
const base = tempDir();
|
|
76
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
77
|
+
try {
|
|
78
|
+
// Should not throw
|
|
79
|
+
releaseSyncLock(base);
|
|
80
|
+
} finally {
|
|
81
|
+
cleanupDir(base);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ─── acquire → release → re-acquire round-trip ───────────────────────────
|
|
86
|
+
|
|
87
|
+
test('sync-lock: can re-acquire after release', () => {
|
|
88
|
+
const base = tempDir();
|
|
89
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
90
|
+
try {
|
|
91
|
+
const r1 = acquireSyncLock(base);
|
|
92
|
+
assert.strictEqual(r1.acquired, true, 'first acquire should succeed');
|
|
93
|
+
releaseSyncLock(base);
|
|
94
|
+
const r2 = acquireSyncLock(base);
|
|
95
|
+
assert.strictEqual(r2.acquired, true, 're-acquire after release should succeed');
|
|
96
|
+
releaseSyncLock(base);
|
|
97
|
+
} finally {
|
|
98
|
+
cleanupDir(base);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ─── stale lock override ─────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
test('sync-lock: overrides stale lock file (mtime backdated)', (t) => {
|
|
105
|
+
const base = tempDir();
|
|
106
|
+
fs.mkdirSync(path.join(base, '.gsd'), { recursive: true });
|
|
107
|
+
const lockPath = path.join(base, '.gsd', 'sync.lock');
|
|
108
|
+
try {
|
|
109
|
+
// Write a lock file with a very old mtime (simulating staleness)
|
|
110
|
+
fs.writeFileSync(lockPath, JSON.stringify({ pid: 99999, acquired_at: new Date(0).toISOString() }));
|
|
111
|
+
// Backdate mtime by 2 minutes
|
|
112
|
+
const staleTime = new Date(Date.now() - 120_000);
|
|
113
|
+
fs.utimesSync(lockPath, staleTime, staleTime);
|
|
114
|
+
|
|
115
|
+
// Should override stale lock and acquire
|
|
116
|
+
const result = acquireSyncLock(base, 500);
|
|
117
|
+
assert.strictEqual(result.acquired, true, 'should acquire over stale lock');
|
|
118
|
+
releaseSyncLock(base);
|
|
119
|
+
} finally {
|
|
120
|
+
cleanupDir(base);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// GSD — unit-ownership tests
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
claimUnit,
|
|
12
|
+
releaseUnit,
|
|
13
|
+
getOwner,
|
|
14
|
+
checkOwnership,
|
|
15
|
+
taskUnitKey,
|
|
16
|
+
sliceUnitKey,
|
|
17
|
+
} from '../unit-ownership.ts';
|
|
18
|
+
|
|
19
|
+
function makeTmpBase(): string {
|
|
20
|
+
return mkdtempSync(join(tmpdir(), 'gsd-ownership-'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cleanup(base: string): void {
|
|
24
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Key builders ────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
test('taskUnitKey: builds correct key', () => {
|
|
30
|
+
assert.equal(taskUnitKey('M001', 'S01', 'T01'), 'M001/S01/T01');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('sliceUnitKey: builds correct key', () => {
|
|
34
|
+
assert.equal(sliceUnitKey('M001', 'S01'), 'M001/S01');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ─── Claim / get / release ───────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
test('claimUnit: creates claim file and records agent', () => {
|
|
40
|
+
const base = makeTmpBase();
|
|
41
|
+
try {
|
|
42
|
+
claimUnit(base, 'M001/S01/T01', 'executor-01');
|
|
43
|
+
|
|
44
|
+
assert.ok(existsSync(join(base, '.gsd', 'unit-claims.json')), 'claim file should exist');
|
|
45
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), 'executor-01');
|
|
46
|
+
} finally {
|
|
47
|
+
cleanup(base);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('claimUnit: overwrites existing claim (last writer wins)', () => {
|
|
52
|
+
const base = makeTmpBase();
|
|
53
|
+
try {
|
|
54
|
+
claimUnit(base, 'M001/S01/T01', 'executor-01');
|
|
55
|
+
claimUnit(base, 'M001/S01/T01', 'executor-02');
|
|
56
|
+
|
|
57
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), 'executor-02');
|
|
58
|
+
} finally {
|
|
59
|
+
cleanup(base);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('claimUnit: multiple units can be claimed independently', () => {
|
|
64
|
+
const base = makeTmpBase();
|
|
65
|
+
try {
|
|
66
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
67
|
+
claimUnit(base, 'M001/S01/T02', 'agent-b');
|
|
68
|
+
|
|
69
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), 'agent-a');
|
|
70
|
+
assert.equal(getOwner(base, 'M001/S01/T02'), 'agent-b');
|
|
71
|
+
} finally {
|
|
72
|
+
cleanup(base);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('getOwner: returns null when no claim file exists', () => {
|
|
77
|
+
const base = makeTmpBase();
|
|
78
|
+
try {
|
|
79
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), null);
|
|
80
|
+
} finally {
|
|
81
|
+
cleanup(base);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('getOwner: returns null for unclaimed unit', () => {
|
|
86
|
+
const base = makeTmpBase();
|
|
87
|
+
try {
|
|
88
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
89
|
+
assert.equal(getOwner(base, 'M001/S01/T99'), null);
|
|
90
|
+
} finally {
|
|
91
|
+
cleanup(base);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('releaseUnit: removes claim', () => {
|
|
96
|
+
const base = makeTmpBase();
|
|
97
|
+
try {
|
|
98
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
99
|
+
releaseUnit(base, 'M001/S01/T01');
|
|
100
|
+
|
|
101
|
+
assert.equal(getOwner(base, 'M001/S01/T01'), null);
|
|
102
|
+
} finally {
|
|
103
|
+
cleanup(base);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('releaseUnit: no-op for non-existent claim', () => {
|
|
108
|
+
const base = makeTmpBase();
|
|
109
|
+
try {
|
|
110
|
+
// Should not throw
|
|
111
|
+
releaseUnit(base, 'M001/S01/T01');
|
|
112
|
+
} finally {
|
|
113
|
+
cleanup(base);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ─── checkOwnership ──────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
test('checkOwnership: returns null when no actorName provided (opt-in)', () => {
|
|
120
|
+
const base = makeTmpBase();
|
|
121
|
+
try {
|
|
122
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
123
|
+
|
|
124
|
+
// No actorName → ownership not enforced
|
|
125
|
+
assert.equal(checkOwnership(base, 'M001/S01/T01', undefined), null);
|
|
126
|
+
} finally {
|
|
127
|
+
cleanup(base);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('checkOwnership: returns null when no claim file exists', () => {
|
|
132
|
+
const base = makeTmpBase();
|
|
133
|
+
try {
|
|
134
|
+
assert.equal(checkOwnership(base, 'M001/S01/T01', 'agent-a'), null);
|
|
135
|
+
} finally {
|
|
136
|
+
cleanup(base);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('checkOwnership: returns null when unit is unclaimed', () => {
|
|
141
|
+
const base = makeTmpBase();
|
|
142
|
+
try {
|
|
143
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
144
|
+
|
|
145
|
+
// Different unit, unclaimed
|
|
146
|
+
assert.equal(checkOwnership(base, 'M001/S01/T99', 'agent-b'), null);
|
|
147
|
+
} finally {
|
|
148
|
+
cleanup(base);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('checkOwnership: returns null when actor matches owner', () => {
|
|
153
|
+
const base = makeTmpBase();
|
|
154
|
+
try {
|
|
155
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
156
|
+
|
|
157
|
+
assert.equal(checkOwnership(base, 'M001/S01/T01', 'agent-a'), null);
|
|
158
|
+
} finally {
|
|
159
|
+
cleanup(base);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('checkOwnership: returns error string when actor does not match owner', () => {
|
|
164
|
+
const base = makeTmpBase();
|
|
165
|
+
try {
|
|
166
|
+
claimUnit(base, 'M001/S01/T01', 'agent-a');
|
|
167
|
+
|
|
168
|
+
const err = checkOwnership(base, 'M001/S01/T01', 'agent-b');
|
|
169
|
+
assert.ok(err !== null, 'should return error');
|
|
170
|
+
assert.match(err!, /owned by agent-a/);
|
|
171
|
+
assert.match(err!, /not agent-b/);
|
|
172
|
+
} finally {
|
|
173
|
+
cleanup(base);
|
|
174
|
+
}
|
|
175
|
+
});
|