omkit 0.0.14 → 0.0.15
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/orchestration/assert.d.ts +6 -4
- package/dist/orchestration/assert.js +7 -7
- package/dist/orchestration/spin/SpinHost.d.ts +2 -0
- package/dist/orchestration/spin/SpinHost.js +23 -15
- package/dist/output/log/RunLog.d.ts +8 -1
- package/dist/output/log/RunLog.js +11 -1
- package/package.json +1 -1
|
@@ -15,8 +15,10 @@ export declare class AssertionError extends Error {
|
|
|
15
15
|
/**
|
|
16
16
|
* Build an {@link Assert} bound to one action (or the spin body): `name` is its
|
|
17
17
|
* launch path (used for attribution in the log line and the error), `logs` is the
|
|
18
|
-
* logger to append to, and `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* logger to append to, and `onResult` is invoked once per call with the pass
|
|
19
|
+
* boolean (and, on failure, an {@link AssertionError} to record into the verdict) —
|
|
20
|
+
* so the host can tally passes/failures and record faults in one place. The factory
|
|
21
|
+
* is file-agnostic — it only appends `level: "assert"` entries; the dedicated
|
|
22
|
+
* `assertions.log` is a downstream projection of that stream.
|
|
21
23
|
*/
|
|
22
|
-
export declare function createAssert(name: string, logs: Logger,
|
|
24
|
+
export declare function createAssert(name: string, logs: Logger, onResult: (pass: boolean, error?: AssertionError) => void): Assert;
|
|
@@ -8,19 +8,19 @@ export class AssertionError extends Error {
|
|
|
8
8
|
/**
|
|
9
9
|
* Build an {@link Assert} bound to one action (or the spin body): `name` is its
|
|
10
10
|
* launch path (used for attribution in the log line and the error), `logs` is the
|
|
11
|
-
* logger to append to, and `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* logger to append to, and `onResult` is invoked once per call with the pass
|
|
12
|
+
* boolean (and, on failure, an {@link AssertionError} to record into the verdict) —
|
|
13
|
+
* so the host can tally passes/failures and record faults in one place. The factory
|
|
14
|
+
* is file-agnostic — it only appends `level: "assert"` entries; the dedicated
|
|
15
|
+
* `assertions.log` is a downstream projection of that stream.
|
|
14
16
|
*/
|
|
15
|
-
export function createAssert(name, logs,
|
|
17
|
+
export function createAssert(name, logs, onResult) {
|
|
16
18
|
return (value, what) => {
|
|
17
19
|
const pass = value;
|
|
18
20
|
const glyph = pass ? "✓" : "✗";
|
|
19
21
|
const message = `${name} · ${glyph} ${what}`;
|
|
20
22
|
logs.append({ source: "assert", level: "assert", message });
|
|
21
|
-
|
|
22
|
-
onFail(new AssertionError(`${name}: ${what}`));
|
|
23
|
-
}
|
|
23
|
+
onResult(pass, pass ? undefined : new AssertionError(`${name}: ${what}`));
|
|
24
24
|
return pass;
|
|
25
25
|
};
|
|
26
26
|
}
|
|
@@ -41,6 +41,8 @@ export declare class SpinHost {
|
|
|
41
41
|
/** The current lifecycle state — see {@link SpinState}. */
|
|
42
42
|
get state(): SpinState;
|
|
43
43
|
private readonly failures;
|
|
44
|
+
private assertionsPassed;
|
|
45
|
+
private assertionsFailed;
|
|
44
46
|
private readonly inFlight;
|
|
45
47
|
private readonly launched;
|
|
46
48
|
private readonly streaming;
|
|
@@ -78,6 +78,9 @@ export class SpinHost {
|
|
|
78
78
|
}
|
|
79
79
|
// Genuine failures (not teardown cascade). Frozen into the verdict at close.
|
|
80
80
|
failures = [];
|
|
81
|
+
// Running tally of every assertion this run made, for the run.log summary footer.
|
|
82
|
+
assertionsPassed = 0;
|
|
83
|
+
assertionsFailed = 0;
|
|
81
84
|
// Every launched action's promise lives here until it settles; the drive loop
|
|
82
85
|
// empties this set. Daemons keep it non-empty until the controller aborts.
|
|
83
86
|
inFlight = new Set();
|
|
@@ -240,15 +243,20 @@ export class SpinHost {
|
|
|
240
243
|
this.nodInto(child, path);
|
|
241
244
|
return child;
|
|
242
245
|
};
|
|
243
|
-
// A boolean check bound to this action's path: it logs a `⊨` milestone,
|
|
244
|
-
//
|
|
245
|
-
// — skipped during teardown (cascade,
|
|
246
|
-
// `scoped`) the same way `record` below does:
|
|
247
|
-
// the message via `name`, so routing through
|
|
248
|
-
// `source` field for a nodded child.
|
|
249
|
-
const assert = createAssert(path, this.logs, (error) => {
|
|
250
|
-
if (
|
|
251
|
-
this.
|
|
246
|
+
// A boolean check bound to this action's path: it logs a `⊨` milestone, tallies
|
|
247
|
+
// the result for the run.log summary, and on failure records a verdict failure
|
|
248
|
+
// the same way an action's own failure does — skipped during teardown (cascade,
|
|
249
|
+
// not a fault). Uses `this.logs` (not `scoped`) the same way `record` below does:
|
|
250
|
+
// the path is already embedded in the message via `name`, so routing through
|
|
251
|
+
// `scoped` would double-prefix the `source` field for a nodded child.
|
|
252
|
+
const assert = createAssert(path, this.logs, (pass, error) => {
|
|
253
|
+
if (pass)
|
|
254
|
+
this.assertionsPassed++;
|
|
255
|
+
else {
|
|
256
|
+
this.assertionsFailed++;
|
|
257
|
+
if (!this.controller.signal.aborted && error)
|
|
258
|
+
this.recordFailure(path, error);
|
|
259
|
+
}
|
|
252
260
|
});
|
|
253
261
|
this.narrate(`launch ${path}`);
|
|
254
262
|
// The action logs into this run's shared store (entries carry their `source`;
|
|
@@ -318,14 +326,14 @@ export class SpinHost {
|
|
|
318
326
|
// before `writeLog` streams it into the final collapsed run.log.
|
|
319
327
|
this.logs.close();
|
|
320
328
|
await this.streaming;
|
|
321
|
-
|
|
329
|
+
// Hand the assertion tally to `write`, which bubbles a summary + the
|
|
330
|
+
// `assertions.log` link into the run.log footer (only when there were any).
|
|
331
|
+
const file = await this.output.runLog.write({
|
|
332
|
+
passed: this.assertionsPassed,
|
|
333
|
+
failed: this.assertionsFailed,
|
|
334
|
+
});
|
|
322
335
|
// Direct to the terminal — the file's already written, so this line isn't in it.
|
|
323
336
|
this.term(LogRenderer.marker("run", `log → ${file}`));
|
|
324
|
-
// If this run logged any assertions, link the dedicated file beside the main one
|
|
325
|
-
// (only exists when there was at least one assertion). Terminal-only, like `log →`.
|
|
326
|
-
const assertionsFile = this.output.runLog.assertionsLog;
|
|
327
|
-
if (assertionsFile)
|
|
328
|
-
this.term(LogRenderer.marker("assert", `assertions → ${assertionsFile}`));
|
|
329
337
|
const result = { ok: this.failures.length === 0, failures: this.failures };
|
|
330
338
|
// One process = one run = one verdict; reflect pass/fail in the exit code so
|
|
331
339
|
// shells and CI see it without parsing the log. Only set it on failure, so a
|
|
@@ -40,8 +40,15 @@ export declare class RunLog {
|
|
|
40
40
|
*
|
|
41
41
|
* Call only after {@link stream} has closed (the run's log is ended), so `run.jsonl`
|
|
42
42
|
* is complete and no longer being written.
|
|
43
|
+
*
|
|
44
|
+
* When this run logged any assertions, `summary` bubbles a footer into `run.log`
|
|
45
|
+
* (only) — the pass/fail tally and a link to `assertions.log` — so results surface
|
|
46
|
+
* in the persistent log rather than the live process output.
|
|
43
47
|
*/
|
|
44
|
-
write(
|
|
48
|
+
write(summary?: {
|
|
49
|
+
passed: number;
|
|
50
|
+
failed: number;
|
|
51
|
+
}): Promise<string>;
|
|
45
52
|
/** A compact header block for the top of run.log — legend + run identity. */
|
|
46
53
|
private header;
|
|
47
54
|
}
|
|
@@ -86,8 +86,12 @@ export class RunLog {
|
|
|
86
86
|
*
|
|
87
87
|
* Call only after {@link stream} has closed (the run's log is ended), so `run.jsonl`
|
|
88
88
|
* is complete and no longer being written.
|
|
89
|
+
*
|
|
90
|
+
* When this run logged any assertions, `summary` bubbles a footer into `run.log`
|
|
91
|
+
* (only) — the pass/fail tally and a link to `assertions.log` — so results surface
|
|
92
|
+
* in the persistent log rather than the live process output.
|
|
89
93
|
*/
|
|
90
|
-
async write() {
|
|
94
|
+
async write(summary) {
|
|
91
95
|
await this.folder.ensure();
|
|
92
96
|
const jsonlFile = this.folder.resolve("run.jsonl");
|
|
93
97
|
const file = this.folder.resolve("run.log");
|
|
@@ -126,6 +130,12 @@ export class RunLog {
|
|
|
126
130
|
}
|
|
127
131
|
for (const item of renderer.end())
|
|
128
132
|
emit(item);
|
|
133
|
+
// Bubble the assertion summary + the dedicated-log link into the run.log footer —
|
|
134
|
+
// only when this run logged assertions (`assertionsPath` is set on the first one).
|
|
135
|
+
if (this.assertionsPath && summary) {
|
|
136
|
+
out.write(LogRenderer.marker("assert", `assertions · ${summary.passed} passed · ${summary.failed} failed`) + "\n");
|
|
137
|
+
out.write(LogRenderer.marker("assert", `assertions → ${this.assertionsPath}`) + "\n");
|
|
138
|
+
}
|
|
129
139
|
await closeStream(out); // flush + close run.log before reporting it written
|
|
130
140
|
await Promise.all(writes); // ensure every off-loaded run is on disk
|
|
131
141
|
return file;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omkit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "A tiny runtime for developer workflows: typed actions that pass runtime capabilities and emit typed events, under one run and one shared log. The operational/runtime layer to tskb's knowledge layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|