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:
+106
-1
@@ -22,6 +22,15 @@ export const STATUS_STATE = "first-views";
|
||||
/** How long a reader asks before it raises its condition naming the owner and the tool (ADR 0615 §6), in ms. */
|
||||
export const BOUND_MS = 2 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* How long a part waits for a view before it writes its finding view-waits (design 47 *A part with no view says it
|
||||
* waits*), in ms: the Go side's `views.WaitsBound`, so an owner down for a restart raises nothing.
|
||||
*/
|
||||
export const WAITS_BOUND_MS = 5 * 60 * 1000;
|
||||
|
||||
/** The kind of a part's finding of a view it has waited for past WAITS_BOUND_MS: the Go side's `views.ViewWaitsKind`. */
|
||||
export const VIEW_WAITS_KIND = "view-waits";
|
||||
|
||||
/** The key of a reader's status of its first view of `owner`'s `state`. */
|
||||
export function statusKey(owner: string, state: string): string {
|
||||
return `${owner}.${state}`;
|
||||
@@ -79,6 +88,15 @@ export interface Status {
|
||||
behind?: string;
|
||||
why?: string;
|
||||
error?: string;
|
||||
/**
|
||||
* The revision of the revision view the part holds from this first view, as its seat's client names it (design 47 *A
|
||||
* part with no view says it waits*); `waits-for` the one it waits for and acts on nothing new until it holds, since
|
||||
* `waiting-since`; `waits-raised` says its view-waits finding is written (past WAITS_BOUND_MS).
|
||||
*/
|
||||
view?: string;
|
||||
"waits-for"?: string;
|
||||
"waiting-since"?: string;
|
||||
"waits-raised"?: boolean;
|
||||
}
|
||||
|
||||
/** How a part makes a follower: the owner and the tool it asks (for the status and the log), and the hooks. */
|
||||
@@ -91,6 +109,9 @@ export interface FollowerOptions {
|
||||
/** The reader's condition of this first view: raised past the bound, cleared by the answer. */
|
||||
readonly raise?: (s: Status) => void | Promise<void>;
|
||||
readonly clear?: (s: Status) => void | Promise<void>;
|
||||
/** The part's view-waits finding: written past WAITS_BOUND_MS of a waitFor, cleared when it holds the view. */
|
||||
readonly raiseWaits?: (s: Status) => void | Promise<void>;
|
||||
readonly clearWaits?: (s: Status) => void | Promise<void>;
|
||||
/** Told of each key the picture changes, after the change is held. */
|
||||
readonly changed?: (key: string, value: unknown, deleted: boolean, at: string) => void | Promise<void>;
|
||||
/** Told of the status each time an ask starts, is answered or fails, and of each event taken: where a reader keeps it. */
|
||||
@@ -103,6 +124,8 @@ export interface FollowerOptions {
|
||||
readonly wait?: number;
|
||||
/** Waits ms between two asks; a test hands one that does not sleep. */
|
||||
readonly pause?: (ms: number) => Promise<void>;
|
||||
/** Waits out WAITS_BOUND_MS of a waitFor; a test hands one it ends itself. Absent: a timer that holds no process open. */
|
||||
readonly waitsBound?: (ms: number) => Promise<void>;
|
||||
}
|
||||
|
||||
const ms = (t: string | undefined): number => (t ? Date.parse(t) || 0 : 0);
|
||||
@@ -194,6 +217,53 @@ export class Follower {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Say the part waits for `view`, a revision of the revision view its seat's client names, and acts on nothing new
|
||||
* until it holds it (holds): the status says what and since when, and if it still waits for it past WAITS_BOUND_MS,
|
||||
* raiseWaits is told (the part writes view-waits, viewWaitsFinding). Waiting for the view it waits for already changes
|
||||
* nothing, not even since when. Resolves once the status is said; the bound runs on by itself.
|
||||
*/
|
||||
async waitFor(view: string): Promise<void> {
|
||||
if (!view || this.st["waits-for"] === view) return;
|
||||
this.st["waits-for"] = view;
|
||||
this.st["waiting-since"] = this.now();
|
||||
this.st["waits-raised"] = false;
|
||||
this.log(`views: ${this.o.tool} waits for ${this.o.owner}'s view ${view}, from ${this.st["waiting-since"]}`);
|
||||
await this.say();
|
||||
void this.boundWaits(view);
|
||||
}
|
||||
|
||||
private async boundWaits(view: string): Promise<void> {
|
||||
const bound = this.o.waitsBound ?? ((wait: number) => new Promise<void>((resolve) => setTimeout(resolve, wait).unref?.()));
|
||||
await bound(WAITS_BOUND_MS);
|
||||
if (this.closed || this.st["waits-for"] !== view || this.st["waits-raised"]) return;
|
||||
this.st["waits-raised"] = true;
|
||||
this.log(`views: ${this.o.tool} has waited for ${this.o.owner}'s view ${view} since ${this.st["waiting-since"]}`);
|
||||
try {
|
||||
await this.o.raiseWaits?.(this.status());
|
||||
} catch (err) {
|
||||
this.log(`views: the view-waits of ${this.o.tool} is not written: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
await this.say();
|
||||
}
|
||||
|
||||
/** Say the part holds `view` now: it waits no more, and a raised view-waits is cleared (clearWaits). */
|
||||
async holds(view: string): Promise<void> {
|
||||
const raised = !!this.st["waits-raised"];
|
||||
this.st.view = view;
|
||||
delete this.st["waits-for"];
|
||||
delete this.st["waiting-since"];
|
||||
delete this.st["waits-raised"];
|
||||
if (raised) {
|
||||
try {
|
||||
await this.o.clearWaits?.(this.status());
|
||||
} catch (err) {
|
||||
this.log(`views: the view-waits of ${this.o.tool} is not cleared: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
}
|
||||
await this.say();
|
||||
}
|
||||
|
||||
/** Ask the first view until it is answered (ADR 0615 §6); resolves once answered, or once the follower is closed. */
|
||||
start(): Promise<void> {
|
||||
return this.askUntil("start", false);
|
||||
@@ -378,6 +448,40 @@ export class Follower {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What a views part says is wrong with a state it follows, in plain fields, as the Go side's `views.Finding`: the writer
|
||||
* the part gives encodes it as the condition keeper reads it, which is the seat mesh-conditions' contract, not this
|
||||
* package's (novox/hq ADR 0634 §4).
|
||||
*/
|
||||
export interface Finding {
|
||||
readonly observer: string;
|
||||
readonly kind: string;
|
||||
readonly id: string;
|
||||
readonly summary: string;
|
||||
readonly headline: string;
|
||||
readonly explanation: string;
|
||||
readonly at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A part's finding of the view it has waited for since `s["waiting-since"]` (design 47), as the Go side's
|
||||
* `views.ViewWaitsFinding`: it names the part, the owner and the revision, and clears when the part holds it.
|
||||
*/
|
||||
export function viewWaitsFinding(part: string, s: Status, at: Date): Finding {
|
||||
const since = s["waiting-since"] ? new Date(s["waiting-since"]).toISOString().replace(/\.\d{3}Z$/, "Z") : "";
|
||||
return {
|
||||
observer: part,
|
||||
kind: VIEW_WAITS_KIND,
|
||||
id: `${part}.${s.owner}.${s.state}`,
|
||||
summary: `${part} waits for ${s.owner}'s view ${s["waits-for"] ?? ""} since ${since} and acts on nothing new until it holds it; it holds ${s.view || "none"}`,
|
||||
headline: `${part} waits for a view of ${s.owner}`,
|
||||
explanation:
|
||||
`${part} keeps running what it runs and takes up nothing new until ${s.owner}'s view ${s["waits-for"] ?? ""} reaches it. Look at ${s.owner}: is it running, and has it ` +
|
||||
`said that revision? ${part}'s first views tool says what it holds and waits for.`,
|
||||
at: at.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/** A Follower's `said` that keeps its status at statusKey in the reader's state STATUS_STATE. */
|
||||
export function keepStatus(st: State<Status>): (s: Status) => Promise<void> {
|
||||
return async (s) => {
|
||||
@@ -413,7 +517,8 @@ export function statusTools(
|
||||
`${reader}'s first views of the owners' states it follows (novox/hq ADR 0615): per owner and state, the tool it asks, the ` +
|
||||
"revision its picture holds now (events taken in included), the highest revision it heard and why it is behind it, if it " +
|
||||
"is, when it asked and was answered, how many asks (by hand among them) and whether its condition (unanswered past two " +
|
||||
"minutes) is raised.",
|
||||
"minutes) is raised; and the revision of the revision view it holds, the one it waits for and acts on nothing new until it " +
|
||||
"holds, since when, and whether its view-waits finding (past five minutes) is written.",
|
||||
input: { type: "object", properties: {} },
|
||||
run: async () => {
|
||||
const out: Status[] = [];
|
||||
|
||||
Reference in New Issue
Block a user