@namzu/sdk 14.0.1 → 14.0.2
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/CHANGELOG.md +25 -0
- package/dist/store/task/__tests__/an-edge-that-already-exists-is-not-news.test.d.ts +2 -0
- package/dist/store/task/__tests__/an-edge-that-already-exists-is-not-news.test.d.ts.map +1 -0
- package/dist/store/task/__tests__/an-edge-that-already-exists-is-not-news.test.js +95 -0
- package/dist/store/task/__tests__/an-edge-that-already-exists-is-not-news.test.js.map +1 -0
- package/dist/store/task/memory.d.ts.map +1 -1
- package/dist/store/task/memory.js +14 -2
- package/dist/store/task/memory.js.map +1 -1
- package/package.json +1 -1
- package/src/store/task/__tests__/an-edge-that-already-exists-is-not-news.test.ts +115 -0
- package/src/store/task/memory.ts +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 14.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9e03c89: `InMemoryTaskStore.block()` stops announcing an edge that already existed.
|
|
8
|
+
|
|
9
|
+
The two task stores disagreed about what `task.updated` means. `block()` is
|
|
10
|
+
idempotent on both — each guards its array against a duplicate entry — but only
|
|
11
|
+
the disk store guarded the _announcement_. Calling `block(a, b)` twice emitted
|
|
12
|
+
two `task.updated` events from the in-memory store and one from the disk store,
|
|
13
|
+
which is the one a host runs in production.
|
|
14
|
+
|
|
15
|
+
So a host rebuilding a dependency graph from the event stream did redundant
|
|
16
|
+
work against the reference implementation and not the durable one, and a host
|
|
17
|
+
counting events to detect change saw change where there was none. The
|
|
18
|
+
divergence is the kind that stays invisible until a store is swapped.
|
|
19
|
+
|
|
20
|
+
The disk store's behaviour was already correct and is now the behaviour of
|
|
21
|
+
both. No event is lost: a call that establishes a new edge — including one that
|
|
22
|
+
repairs a half-edge, where only one of the two arrays grows — still announces
|
|
23
|
+
both ends, because both ends are one fact.
|
|
24
|
+
|
|
25
|
+
The disk store's side of this had no test, which is why the disagreement
|
|
26
|
+
survived. Both are now driven from the same cases.
|
|
27
|
+
|
|
3
28
|
## 14.0.1
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"an-edge-that-already-exists-is-not-news.test.d.ts","sourceRoot":"","sources":["../../../../src/store/task/__tests__/an-edge-that-already-exists-is-not-news.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { DiskTaskStore } from '../disk.js';
|
|
6
|
+
import { InMemoryTaskStore } from '../memory.js';
|
|
7
|
+
/**
|
|
8
|
+
* The two stores disagreed about what `task.updated` means.
|
|
9
|
+
*
|
|
10
|
+
* `block()` is idempotent on both — it guards each array against a duplicate
|
|
11
|
+
* entry — but only the disk store guarded the *announcement*. Calling
|
|
12
|
+
* `block(a, b)` twice emitted two `task.updated` events from the in-memory
|
|
13
|
+
* store and one from the disk store, which is the one a host runs in
|
|
14
|
+
* production. So a host rebuilding a dependency graph from the stream did
|
|
15
|
+
* redundant work against the reference implementation and not the durable one,
|
|
16
|
+
* and a host counting events to detect change saw change where there was none.
|
|
17
|
+
*
|
|
18
|
+
* The disk store's behaviour is the correct one and the in-memory store now
|
|
19
|
+
* matches it: an edge that already existed is not news.
|
|
20
|
+
*
|
|
21
|
+
* Both are driven from the same cases here, because a reference implementation
|
|
22
|
+
* that disagrees with the durable one is worse than having only one — the
|
|
23
|
+
* disagreement is invisible until a host swaps stores in production.
|
|
24
|
+
*/
|
|
25
|
+
const RUN = 'run_edge';
|
|
26
|
+
const dirs = [];
|
|
27
|
+
afterEach(async () => {
|
|
28
|
+
await Promise.all(dirs.map((d) => rm(d, { recursive: true, force: true })));
|
|
29
|
+
dirs.length = 0;
|
|
30
|
+
});
|
|
31
|
+
async function diskStore() {
|
|
32
|
+
const dir = await mkdtemp(join(tmpdir(), 'namzu-edge-'));
|
|
33
|
+
dirs.push(dir);
|
|
34
|
+
return new DiskTaskStore({ baseDir: dir, defaultRunId: RUN });
|
|
35
|
+
}
|
|
36
|
+
const IMPLEMENTATIONS = [
|
|
37
|
+
['in memory', async () => new InMemoryTaskStore()],
|
|
38
|
+
['on disk', diskStore],
|
|
39
|
+
];
|
|
40
|
+
describe.each(IMPLEMENTATIONS)('an edge that already exists is not news (%s)', (_n, build) => {
|
|
41
|
+
async function twoTasks(store) {
|
|
42
|
+
const blocker = await store.create({ runId: RUN, subject: 'blocker' });
|
|
43
|
+
const blocked = await store.create({ runId: RUN, subject: 'blocked' });
|
|
44
|
+
return { blocker, blocked };
|
|
45
|
+
}
|
|
46
|
+
function recordUpdates(store) {
|
|
47
|
+
const seen = [];
|
|
48
|
+
store.on((event) => {
|
|
49
|
+
if (event.type === 'task.updated')
|
|
50
|
+
seen.push(event);
|
|
51
|
+
});
|
|
52
|
+
return seen;
|
|
53
|
+
}
|
|
54
|
+
it('announces both ends when the edge is written', async () => {
|
|
55
|
+
// The control: without it, "no second announcement" could be passing
|
|
56
|
+
// because nothing announces at all.
|
|
57
|
+
const store = await build();
|
|
58
|
+
const { blocker, blocked } = await twoTasks(store);
|
|
59
|
+
const seen = recordUpdates(store);
|
|
60
|
+
await store.block(blocker.id, blocked.id);
|
|
61
|
+
expect(seen.map((e) => e.taskId)).toEqual([blocker.id, blocked.id]);
|
|
62
|
+
});
|
|
63
|
+
it('says nothing the second time', async () => {
|
|
64
|
+
const store = await build();
|
|
65
|
+
const { blocker, blocked } = await twoTasks(store);
|
|
66
|
+
await store.block(blocker.id, blocked.id);
|
|
67
|
+
const seen = recordUpdates(store);
|
|
68
|
+
await store.block(blocker.id, blocked.id);
|
|
69
|
+
expect(seen).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
it('still records the edge exactly once after a repeated call', async () => {
|
|
72
|
+
// Suppressing the event must not become suppressing the write, and the
|
|
73
|
+
// arrays must not grow a duplicate.
|
|
74
|
+
const store = await build();
|
|
75
|
+
const { blocker, blocked } = await twoTasks(store);
|
|
76
|
+
await store.block(blocker.id, blocked.id);
|
|
77
|
+
await store.block(blocker.id, blocked.id);
|
|
78
|
+
expect((await store.get(blocker.id))?.blocks).toEqual([blocked.id]);
|
|
79
|
+
expect((await store.get(blocked.id))?.blockedBy).toEqual([blocker.id]);
|
|
80
|
+
});
|
|
81
|
+
it('announces a repair when only one side of the edge was missing', async () => {
|
|
82
|
+
// A half-edge is the case the early return must not swallow: one array
|
|
83
|
+
// already names the other and its counterpart does not. Both ends are
|
|
84
|
+
// one fact, so both are announced even though only one array grew.
|
|
85
|
+
const store = await build();
|
|
86
|
+
const { blocker, blocked } = await twoTasks(store);
|
|
87
|
+
await store.block(blocker.id, blocked.id);
|
|
88
|
+
// Reverse direction: `blocked` has not yet been recorded as blocking
|
|
89
|
+
// `blocker`, so this is a new edge, not the existing one.
|
|
90
|
+
const seen = recordUpdates(store);
|
|
91
|
+
await store.block(blocked.id, blocker.id);
|
|
92
|
+
expect(seen.map((e) => e.taskId)).toEqual([blocked.id, blocker.id]);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=an-edge-that-already-exists-is-not-news.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"an-edge-that-already-exists-is-not-news.test.js","sourceRoot":"","sources":["../../../../src/store/task/__tests__/an-edge-that-already-exists-is-not-news.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAIxD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAEhD;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,GAAG,GAAG,UAAmB,CAAA;AAE/B,MAAM,IAAI,GAAa,EAAE,CAAA;AACzB,SAAS,CAAC,KAAK,IAAI,EAAE;IACpB,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3E,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;AAChB,CAAC,CAAC,CAAA;AAEF,KAAK,UAAU,SAAS;IACvB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,aAAa,CAAC,CAAC,CAAA;IACxD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACd,OAAO,IAAI,aAAa,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,eAAe,GAA+D;IACnF,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,iBAAiB,EAAE,CAAC;IAClD,CAAC,SAAS,EAAE,SAAS,CAAC;CACtB,CAAA;AAED,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,8CAA8C,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE;IAC5F,KAAK,UAAU,QAAQ,CAAC,KAAgB;QACvC,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QACtE,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QACtE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IAC5B,CAAC;IAED,SAAS,aAAa,CAAC,KAAgB;QACtC,MAAM,IAAI,GAAgB,EAAE,CAAA;QAC5B,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE;YAClB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;gBAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACpD,CAAC,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC7D,qEAAqE;QACrE,oCAAoC;QACpC,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAA;QAC3B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAA;QAClD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QAEjC,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QAEzC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAA;QAC3B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAA;QAClD,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QACzC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QAEjC,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QAEzC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QAC1E,uEAAuE;QACvE,oCAAoC;QACpC,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAA;QAC3B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAA;QAElD,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QACzC,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QAEzC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;QACnE,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;IACvE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC9E,uEAAuE;QACvE,sEAAsE;QACtE,mEAAmE;QACnE,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAA;QAC3B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAA;QAClD,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QACzC,qEAAqE;QACrE,0DAA0D;QAC1D,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;QAEjC,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;QAEzC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/store/task/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EACX,gBAAgB,EAChB,IAAI,EAEJ,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,MAAM,2BAA2B,CAAA;AAmBlC,qBAAa,iBAAkB,YAAW,SAAS;IAClD,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,SAAS,CAA0B;IAE3C,EAAE,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAO3C,OAAO,CAAC,IAAI;IAQN,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B/C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAI1C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAwCxE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBpC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAgBtF,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAc3D,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/store/task/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,KAAK,EACX,gBAAgB,EAChB,IAAI,EAEJ,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,MAAM,2BAA2B,CAAA;AAmBlC,qBAAa,iBAAkB,YAAW,SAAS;IAClD,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,SAAS,CAA0B;IAE3C,EAAE,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAO3C,OAAO,CAAC,IAAI;IAQN,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B/C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAI1C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAwCxE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBpC,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAgBtF,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAc3D,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
|
|
@@ -150,17 +150,29 @@ export class InMemoryTaskStore {
|
|
|
150
150
|
const blocked = this.tasks.get(blockedId);
|
|
151
151
|
if (!blocker || !blocked)
|
|
152
152
|
return;
|
|
153
|
+
let mutated = false;
|
|
153
154
|
if (!blocker.blocks.includes(blockedId)) {
|
|
154
155
|
blocker.blocks.push(blockedId);
|
|
156
|
+
mutated = true;
|
|
155
157
|
}
|
|
156
158
|
if (!blocked.blockedBy.includes(blockerId)) {
|
|
157
159
|
blocked.blockedBy.push(blockerId);
|
|
160
|
+
mutated = true;
|
|
158
161
|
}
|
|
162
|
+
// An edge that already existed is not news. This used to announce
|
|
163
|
+
// unconditionally while the disk store — the one a host runs in
|
|
164
|
+
// production — returned early, so the same call sequence produced two
|
|
165
|
+
// events from one implementation and none from the other. A host
|
|
166
|
+
// rebuilding a graph from the stream did redundant work against one
|
|
167
|
+
// store and not the other, and a host counting events to detect change
|
|
168
|
+
// saw change where there was none.
|
|
169
|
+
if (!mutated)
|
|
170
|
+
return;
|
|
159
171
|
// Announce BOTH ends. The edge was written and nothing said so, which
|
|
160
172
|
// left the graph observable only by polling: a listener saw a unit
|
|
161
173
|
// created and never learned that something now waits on it. Both sides
|
|
162
|
-
//
|
|
163
|
-
// half the edge.
|
|
174
|
+
// are one fact, so both are announced even when only one array grew —
|
|
175
|
+
// a host tracking a single side would draw half the edge.
|
|
164
176
|
const now = Date.now();
|
|
165
177
|
this.emit({ type: 'task.updated', taskId: blockerId, task: blocker, timestamp: now });
|
|
166
178
|
this.emit({ type: 'task.updated', taskId: blockedId, task: blocked, timestamp: now });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/store/task/memory.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/store/task/memory.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAElD,sEAAsE;AACtE,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,qCAAqC;AACrC,MAAM,YAAY,GAA+B;IAChD,OAAO,EAAE,CAAC;IACV,WAAW,EAAE,CAAC;IACd,SAAS,EAAE,CAAC;IACZ,MAAM,EAAE,CAAC;CACT,CAAA;AAED,SAAS,mBAAmB,CAAC,IAAgB,EAAE,EAAc;IAC5D,OAAO,YAAY,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,OAAO,iBAAiB;IACrB,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAA;IAC/B,SAAS,GAAwB,EAAE,CAAA;IAE3C,EAAE,CAAC,QAA2B;QAC7B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC7B,OAAO,GAAG,EAAE;YACX,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAA;QAC9D,CAAC,CAAA;IACF,CAAC;IAEO,IAAI,CAAC,KAAgB;QAC5B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,IAAI,CAAC;gBACJ,QAAQ,CAAC,KAAK,CAAC,CAAA;YAChB,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;IACF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAwB;QACpC,MAAM,IAAI,GAAS;YAClB,EAAE,EAAE,cAAc,EAAE;YACpB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,MAAM,EAAE,SAAS;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,EAAE;YACV,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE;YACxD,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS;YAC9D,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACrB,CAAA;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QAE7B,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;gBACzC,IAAI,OAAO,EAAE,CAAC;oBACb,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBAC7B,CAAC;YACF,CAAC;QACF,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACjF,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,OAAyB;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAA;QAE3B,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAA;QAElC,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;QACjE,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS;YAAE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAA;QAC7E,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;QAC1E,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC3D,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ;gBAC5B,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE;gBAC3C,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAA;QAC3B,CAAC;QAED,IACC,OAAO,CAAC,MAAM,KAAK,SAAS;YAC5B,OAAO,CAAC,MAAM,KAAK,cAAc;YACjC,mBAAmB,CAAC,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,EAClD,CAAC;YACF,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;YAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,aAAa,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACzD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YAC5B,CAAC;YACD,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBACpC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YAC9B,CAAC;QACF,CAAC;QAED,IAAI,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,IAAI;YACJ,cAAc;YACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACrB,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAA;QAEvB,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACxC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YACzC,IAAI,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,CAAA;YAC5D,CAAC;QACF,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YACzC,IAAI,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,CAAA;YAClE,CAAC;QACF,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACrB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QAC5E,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAA+D;QACzE,IAAI,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;QAE7C,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;YACpB,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,CAAA;QAC5D,CAAC;QACD,IAAI,MAAM,EAAE,KAAK,EAAE,CAAC;YACnB,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,MAAM,EAAE,KAAK,EAAE,CAAC;YACnB,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1D,CAAC;QAED,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAA;IACzD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAU,EAAE,KAAa;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAA;QAC3B,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAC/C,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAE9C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,MAAM,GAAG,aAAa,CAAA;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAE3B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACjF,OAAO,IAAI,CAAA;IACZ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAiB,EAAE,SAAiB;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACzC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO;YAAE,OAAM;QAEhC,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAC9B,OAAO,GAAG,IAAI,CAAA;QACf,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5C,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YACjC,OAAO,GAAG,IAAI,CAAA;QACf,CAAC;QACD,kEAAkE;QAClE,gEAAgE;QAChE,sEAAsE;QACtE,iEAAiE;QACjE,oEAAoE;QACpE,uEAAuE;QACvE,mCAAmC;QACnC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEpB,sEAAsE;QACtE,mEAAmE;QACnE,uEAAuE;QACvE,sEAAsE;QACtE,0DAA0D;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;QACrF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;IACtF,CAAC;IAED,KAAK,CAAC,KAAK;QACV,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;IACnB,CAAC;CACD"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import type { RunId } from '../../../types/ids/index.js'
|
|
7
|
+
import type { TaskEvent, TaskStore } from '../../../types/task/index.js'
|
|
8
|
+
import { DiskTaskStore } from '../disk.js'
|
|
9
|
+
import { InMemoryTaskStore } from '../memory.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The two stores disagreed about what `task.updated` means.
|
|
13
|
+
*
|
|
14
|
+
* `block()` is idempotent on both — it guards each array against a duplicate
|
|
15
|
+
* entry — but only the disk store guarded the *announcement*. Calling
|
|
16
|
+
* `block(a, b)` twice emitted two `task.updated` events from the in-memory
|
|
17
|
+
* store and one from the disk store, which is the one a host runs in
|
|
18
|
+
* production. So a host rebuilding a dependency graph from the stream did
|
|
19
|
+
* redundant work against the reference implementation and not the durable one,
|
|
20
|
+
* and a host counting events to detect change saw change where there was none.
|
|
21
|
+
*
|
|
22
|
+
* The disk store's behaviour is the correct one and the in-memory store now
|
|
23
|
+
* matches it: an edge that already existed is not news.
|
|
24
|
+
*
|
|
25
|
+
* Both are driven from the same cases here, because a reference implementation
|
|
26
|
+
* that disagrees with the durable one is worse than having only one — the
|
|
27
|
+
* disagreement is invisible until a host swaps stores in production.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const RUN = 'run_edge' as RunId
|
|
31
|
+
|
|
32
|
+
const dirs: string[] = []
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await Promise.all(dirs.map((d) => rm(d, { recursive: true, force: true })))
|
|
35
|
+
dirs.length = 0
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
async function diskStore(): Promise<TaskStore> {
|
|
39
|
+
const dir = await mkdtemp(join(tmpdir(), 'namzu-edge-'))
|
|
40
|
+
dirs.push(dir)
|
|
41
|
+
return new DiskTaskStore({ baseDir: dir, defaultRunId: RUN })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const IMPLEMENTATIONS: ReadonlyArray<readonly [string, () => Promise<TaskStore>]> = [
|
|
45
|
+
['in memory', async () => new InMemoryTaskStore()],
|
|
46
|
+
['on disk', diskStore],
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
describe.each(IMPLEMENTATIONS)('an edge that already exists is not news (%s)', (_n, build) => {
|
|
50
|
+
async function twoTasks(store: TaskStore) {
|
|
51
|
+
const blocker = await store.create({ runId: RUN, subject: 'blocker' })
|
|
52
|
+
const blocked = await store.create({ runId: RUN, subject: 'blocked' })
|
|
53
|
+
return { blocker, blocked }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function recordUpdates(store: TaskStore): TaskEvent[] {
|
|
57
|
+
const seen: TaskEvent[] = []
|
|
58
|
+
store.on((event) => {
|
|
59
|
+
if (event.type === 'task.updated') seen.push(event)
|
|
60
|
+
})
|
|
61
|
+
return seen
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
it('announces both ends when the edge is written', async () => {
|
|
65
|
+
// The control: without it, "no second announcement" could be passing
|
|
66
|
+
// because nothing announces at all.
|
|
67
|
+
const store = await build()
|
|
68
|
+
const { blocker, blocked } = await twoTasks(store)
|
|
69
|
+
const seen = recordUpdates(store)
|
|
70
|
+
|
|
71
|
+
await store.block(blocker.id, blocked.id)
|
|
72
|
+
|
|
73
|
+
expect(seen.map((e) => e.taskId)).toEqual([blocker.id, blocked.id])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('says nothing the second time', async () => {
|
|
77
|
+
const store = await build()
|
|
78
|
+
const { blocker, blocked } = await twoTasks(store)
|
|
79
|
+
await store.block(blocker.id, blocked.id)
|
|
80
|
+
const seen = recordUpdates(store)
|
|
81
|
+
|
|
82
|
+
await store.block(blocker.id, blocked.id)
|
|
83
|
+
|
|
84
|
+
expect(seen).toEqual([])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('still records the edge exactly once after a repeated call', async () => {
|
|
88
|
+
// Suppressing the event must not become suppressing the write, and the
|
|
89
|
+
// arrays must not grow a duplicate.
|
|
90
|
+
const store = await build()
|
|
91
|
+
const { blocker, blocked } = await twoTasks(store)
|
|
92
|
+
|
|
93
|
+
await store.block(blocker.id, blocked.id)
|
|
94
|
+
await store.block(blocker.id, blocked.id)
|
|
95
|
+
|
|
96
|
+
expect((await store.get(blocker.id))?.blocks).toEqual([blocked.id])
|
|
97
|
+
expect((await store.get(blocked.id))?.blockedBy).toEqual([blocker.id])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('announces a repair when only one side of the edge was missing', async () => {
|
|
101
|
+
// A half-edge is the case the early return must not swallow: one array
|
|
102
|
+
// already names the other and its counterpart does not. Both ends are
|
|
103
|
+
// one fact, so both are announced even though only one array grew.
|
|
104
|
+
const store = await build()
|
|
105
|
+
const { blocker, blocked } = await twoTasks(store)
|
|
106
|
+
await store.block(blocker.id, blocked.id)
|
|
107
|
+
// Reverse direction: `blocked` has not yet been recorded as blocking
|
|
108
|
+
// `blocker`, so this is a new edge, not the existing one.
|
|
109
|
+
const seen = recordUpdates(store)
|
|
110
|
+
|
|
111
|
+
await store.block(blocked.id, blocker.id)
|
|
112
|
+
|
|
113
|
+
expect(seen.map((e) => e.taskId)).toEqual([blocked.id, blocker.id])
|
|
114
|
+
})
|
|
115
|
+
})
|
package/src/store/task/memory.ts
CHANGED
|
@@ -177,18 +177,29 @@ export class InMemoryTaskStore implements TaskStore {
|
|
|
177
177
|
const blocked = this.tasks.get(blockedId)
|
|
178
178
|
if (!blocker || !blocked) return
|
|
179
179
|
|
|
180
|
+
let mutated = false
|
|
180
181
|
if (!blocker.blocks.includes(blockedId)) {
|
|
181
182
|
blocker.blocks.push(blockedId)
|
|
183
|
+
mutated = true
|
|
182
184
|
}
|
|
183
185
|
if (!blocked.blockedBy.includes(blockerId)) {
|
|
184
186
|
blocked.blockedBy.push(blockerId)
|
|
187
|
+
mutated = true
|
|
185
188
|
}
|
|
189
|
+
// An edge that already existed is not news. This used to announce
|
|
190
|
+
// unconditionally while the disk store — the one a host runs in
|
|
191
|
+
// production — returned early, so the same call sequence produced two
|
|
192
|
+
// events from one implementation and none from the other. A host
|
|
193
|
+
// rebuilding a graph from the stream did redundant work against one
|
|
194
|
+
// store and not the other, and a host counting events to detect change
|
|
195
|
+
// saw change where there was none.
|
|
196
|
+
if (!mutated) return
|
|
186
197
|
|
|
187
198
|
// Announce BOTH ends. The edge was written and nothing said so, which
|
|
188
199
|
// left the graph observable only by polling: a listener saw a unit
|
|
189
200
|
// created and never learned that something now waits on it. Both sides
|
|
190
|
-
//
|
|
191
|
-
// half the edge.
|
|
201
|
+
// are one fact, so both are announced even when only one array grew —
|
|
202
|
+
// a host tracking a single side would draw half the edge.
|
|
192
203
|
const now = Date.now()
|
|
193
204
|
this.emit({ type: 'task.updated', taskId: blockerId, task: blocker, timestamp: now })
|
|
194
205
|
this.emit({ type: 'task.updated', taskId: blockedId, task: blocked, timestamp: now })
|