T-2: W1.4 (TypeScript): a TypeScript part says the revision view it holds and waits for, and view-waits past five minutes

The forge module reads the catalog half from TypeScript (W12.7) and must wait and say so as the Go readers do (design 47,
A part with no view says it waits): views' Follower gains waitFor and holds, raiseWaits and clearWaits, the status fields
view, waits-for, waiting-since and waits-raised, WAITS_BOUND_MS (five minutes) and viewWaitsFinding, by the Go side's
rules (go/views follow.go, findings.go). Rows in the first-view test of test/sdk.test.ts.

Builds: design 47 G §196, §200

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 10:51:07 +01:00
parent 0d546d37f3
commit 2411fb4e12
3 changed files with 152 additions and 3 deletions
+44 -1
View File
@@ -664,7 +664,7 @@ test("the provider loop is named for what it is, and its old name stays an alias
});
import { allows, refusal, parseCallers, parseVerbCallers, parseConfiguredVerbCallers, moduleOf, UNPROVEN } from "../dist/callers/index.js";
import { Follower, statusTools, keepStatus, statusKey, type Answer, type Status } from "../dist/views/index.js";
import { Follower, statusTools, keepStatus, statusKey, viewWaitsFinding, type Answer, type Status } from "../dist/views/index.js";
test("callers and verb callers judge a proved caller by the Go side's rules (ADR 0459 §2, ADR 0627 §2)", () => {
const rows: [string, string[], string, boolean][] = [
@@ -784,4 +784,47 @@ test("a first view follows its owner's events, asks again on a gap and by hand,
// Another created is the state made again: a gap too.
f.close();
assert.equal((await f.apply({ key: "a", revision: 1, created: "2026-11-09T11:00:00.000Z", value: 9 })).gap, true);
// Design 47 *A part with no view says it waits*: the status says the view held, the one waited for and since when;
// past its bound the part's view-waits is raised once, and holding the view clears it.
const ends: (() => void)[] = [];
const bounds: number[] = [];
const wRaised: Status[] = [];
const wCleared: Status[] = [];
const settle = async () => { for (let i = 0; i < 20; i++) await Promise.resolve(); };
const w = new Follower({
owner: "o", tool: "o.revision", state: "catalog", ask, now: () => new Date(created), said: keepStatus(st),
raiseWaits: (s) => void wRaised.push(s), clearWaits: (s) => void wCleared.push(s),
waitsBound: (ms) => (bounds.push(ms), new Promise<void>((resolve) => ends.push(resolve))),
});
await w.holds("trunk.11");
await w.waitFor("trunk.12");
await w.waitFor("trunk.12"); // the same view again: nothing new, no second bound
assert.deepEqual(bounds, [5 * 60 * 1000], "one five-minute bound");
s = w.status();
assert.equal(s.view, "trunk.11");
assert.equal(s["waits-for"], "trunk.12");
assert.equal(s["waiting-since"], created);
assert.equal(s["waits-raised"], false);
assert.equal(kept.get(statusKey("o", "catalog"))!["waits-for"], "trunk.12", "the read tool's state says it");
ends.shift()!();
await settle();
assert.equal(wRaised.length, 1, "past its bound view-waits is raised");
assert.equal(wRaised[0]["waits-for"], "trunk.12");
const fd = viewWaitsFinding("gitea", w.status(), new Date(created));
assert.equal(fd.kind, "view-waits");
assert.equal(fd.id, "gitea.o.catalog");
assert.match(fd.summary, /^gitea waits for o's view trunk\.12 since 2026-11-09T10:00:00Z and acts on nothing new until it holds it; it holds trunk\.11$/);
await w.holds("trunk.12");
s = w.status();
assert.equal(s.view, "trunk.12");
assert.equal(s["waits-for"], undefined);
assert.equal(s["waits-raised"], undefined);
assert.equal(wCleared.length, 1, "holding the view clears it");
await w.waitFor("trunk.13");
await w.holds("trunk.13");
ends.shift()!(); // the bound of a view now held raises nothing
await settle();
assert.equal(wRaised.length, 1, "a view held before its bound raises nothing");
assert.equal(wCleared.length, 1, "nothing raised, nothing cleared");
});