Skip to content

Commit 9dc3b15

Browse files
test(core): cover observation-registry privacy across a parent and subtask
The registry isolation test only showed two standalone registries behaving independently, which says nothing about the parent/subtask contract the write guard is used in, where a subtask is constructed with parentTask and rootTask. Task.spec.ts now builds a parent and a child through the constructor (startTask: false, no API traffic) and asserts that the child points at the parent, carries the parent's taskId as both parentTaskId and rootTaskId, owns a different registry object, holds its own token for the same path, and that forgetting the child's entry leaves the parent's intact. One factual note recorded in the test: Task accepts a rootTask option and derives rootTaskId from it, but never assigns the rootTask object field - ClineProvider.ts:3459 reads that field and ClineProvider.spec.ts:1931 patches it by hand - so the test asserts the id link the constructor actually establishes rather than pretending the object link exists. Negative control (registry shared between instances through a process-level singleton): AssertionError: expected ObservationRegistry{ entries: Map{} } not to be ObservationRegistry{ entries: Map{} } at the identity assertion; Task.ts restored byte-identically (sha256-12 e163f866d049) and re-ran green.
1 parent a4d9f01 commit 9dc3b15

1 file changed

Lines changed: 40 additions & 0 deletions

File tree

‎src/core/task/__tests__/Task.spec.ts‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,46 @@ describe("Cline", () => {
16761676
new Task({ provider: mockProvider, apiConfiguration: mockApiConfig })
16771677
}).toThrow("Either historyItem or task/images must be provided")
16781678
})
1679+
1680+
it("keeps the observation registry private to each task in a parent/subtask pair", () => {
1681+
// The write guard compares an observed token against the file on disk, so a
1682+
// subtask must neither inherit nor overwrite what the parent observed: each
1683+
// Task builds its own registry rather than borrowing the parent's.
1684+
const parent = new Task({
1685+
provider: mockProvider,
1686+
apiConfiguration: mockApiConfig,
1687+
task: "parent task",
1688+
startTask: false,
1689+
})
1690+
const child = new Task({
1691+
provider: mockProvider,
1692+
apiConfiguration: mockApiConfig,
1693+
task: "child task",
1694+
parentTask: parent,
1695+
rootTask: parent,
1696+
startTask: false,
1697+
})
1698+
1699+
// The constructor records the parent object plus the root/parent ids; the
1700+
// rootTask object field is filled in by callers (ClineProvider), not by the
1701+
// constructor, so the id link is what this boundary can assert.
1702+
expect(child.parentTask).toBe(parent)
1703+
expect(child.parentTaskId).toBe(parent.taskId)
1704+
expect(child.rootTaskId).toBe(parent.taskId)
1705+
expect(child.observationRegistry).not.toBe(parent.observationRegistry)
1706+
1707+
// The same path recorded in both tasks, with different tokens.
1708+
parent.observationRegistry.observe("/workspace/shared.ts", "1:2:3:4:5")
1709+
child.observationRegistry.observe("/workspace/shared.ts", "9:8:7:6:5")
1710+
1711+
expect(parent.observationRegistry.get("/workspace/shared.ts")?.version).toBe("1:2:3:4:5")
1712+
expect(child.observationRegistry.get("/workspace/shared.ts")?.version).toBe("9:8:7:6:5")
1713+
1714+
// Neither direction leaks: dropping the child's entry leaves the parent's.
1715+
child.observationRegistry.forget("/workspace/shared.ts")
1716+
expect(child.observationRegistry.get("/workspace/shared.ts")).toBeUndefined()
1717+
expect(parent.observationRegistry.get("/workspace/shared.ts")?.version).toBe("1:2:3:4:5")
1718+
})
16791719
})
16801720

16811721
describe("updateApiConfiguration", () => {

0 commit comments

Comments
 (0)