okengine 0.1.6 → 0.2.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/AGENTS.md +1 -1
- package/README.md +36 -29
- package/docs/spec/example.md +1187 -0
- package/docs/spec/unified-theory.md +1 -1
- package/package.json +20 -6
- package/src/cli/dev-app-runner.ts +6 -2
- package/src/cli/dev.ts +19 -10
- package/src/cli/doc-drift.ts +54 -21
- package/src/console/index.ts +25 -13
- package/src/console/server/app.ts +44 -7
- package/src/console/server/claim.test.ts +7 -4
- package/src/console/server/claim.ts +2 -7
- package/src/console/server/flows.ts +2 -6
- package/src/console/server/index.ts +2 -1
- package/src/console/server/lazy-panels.test.ts +27 -0
- package/src/console/server/panel-load.ts +28 -0
- package/src/console/server/plugin.ts +1 -1
- package/src/console/server/plugins.ts +7 -6
- package/src/console/server/public-flows.ts +12 -0
- package/src/console/server/state.ts +159 -122
- package/src/console/server/store.ts +13 -10
- package/src/console/ui/dist/assets/index-B71Yl_SS.js +10 -0
- package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +64 -0
- package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +1 -0
- package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +1 -0
- package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +1 -0
- package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +1 -0
- package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +1 -0
- package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +45 -0
- package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +1 -0
- package/src/console/ui/dist/assets/panel-overview-Bd48d9km.js +1 -0
- package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-BwsWqKeB.js +1 -0
- package/src/console/ui/dist/assets/panel-signals-9najbZY2.js +1 -0
- package/src/console/ui/dist/assets/panel-store-OHkP2pDp.js +1 -0
- package/src/console/ui/dist/assets/panel-traces-tn2JoY8U.js +1 -0
- package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +1 -0
- package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
- package/src/console/ui/dist/assets/style-Cnl7WLya.css +3 -0
- package/src/console/ui/dist/index.html +14 -0
- package/src/docs-origin.ts +19 -0
- package/src/drivers/index.ts +1 -6
- package/src/drivers/vault-sops.ts +20 -1
- package/src/kernel/app.ts +1 -1
- package/src/kernel/boot-bind/ai.ts +31 -0
- package/src/kernel/boot-bind/channel.ts +27 -0
- package/src/kernel/boot-bind/clock.ts +74 -0
- package/src/kernel/boot-bind/gate.ts +28 -0
- package/src/kernel/boot-bind/runs.ts +28 -0
- package/src/kernel/boot-bind/signal.ts +68 -0
- package/src/kernel/boot-bind/store.ts +47 -0
- package/src/kernel/boot-bind/vault.ts +36 -0
- package/src/kernel/boot.test.ts +85 -1
- package/src/kernel/boot.ts +250 -212
- package/src/kernel/errors.registry.test.ts +4 -2
- package/src/kernel/errors.ts +3 -1
- package/src/kernel/fx.test.ts +2 -2
- package/src/kernel/index.ts +2 -0
- package/src/mcp/data.ts +1 -0
- package/src/mcp/docs-index.ts +252 -0
- package/src/mcp/docs-mcp.test.ts +176 -0
- package/src/mcp/docs-server.ts +233 -0
- package/src/mcp/docs-tools.ts +143 -0
- package/src/mcp/index.ts +29 -5
- package/src/mcp/protocol.ts +2 -1
- package/src/release/exports.test.ts +71 -0
- package/src/release/exports.ts +156 -0
- package/src/release/index.ts +21 -0
- package/src/release/limits.ts +9 -0
- package/src/release/measure.exports.test.ts +82 -0
- package/src/release/measure.ts +297 -14
- package/src/release/publish.ts +14 -3
- package/src/release/readme.test.ts +61 -0
- package/src/release/readme.ts +13 -0
- package/src/runtime/index.ts +1 -0
- package/src/runtime/security.test.ts +6 -1
- package/src/runtime/types.ts +6 -0
- package/src/term.test.ts +51 -0
- package/src/term.ts +158 -0
- package/src/test/create-test-app.ts +2 -0
package/src/release/measure.ts
CHANGED
|
@@ -2,18 +2,55 @@
|
|
|
2
2
|
* Budget measurement helpers — shared by gates and the budgets.json publisher.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { readdir } from "node:fs/promises";
|
|
6
|
-
import {
|
|
5
|
+
import { mkdtemp, readdir, rm } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
7
8
|
import { createRouter } from "../kernel/router.ts";
|
|
9
|
+
import {
|
|
10
|
+
type BudgetGroup,
|
|
11
|
+
resolveExportBudgetTargets,
|
|
12
|
+
} from "./exports.ts";
|
|
8
13
|
import {
|
|
9
14
|
CLIENT_BUDGET_BYTES,
|
|
10
15
|
COLD_START_BUDGET_MS,
|
|
11
16
|
CONSOLE_BUDGET_BYTES,
|
|
17
|
+
EXPORT_REGRESSION_TOLERANCE_FLOOR_BYTES,
|
|
18
|
+
EXPORT_REGRESSION_TOLERANCE_RATIO,
|
|
12
19
|
KERNEL_EDGE_BUDGET_BYTES,
|
|
13
20
|
ROUTING_P99_BUDGET_MS,
|
|
14
21
|
} from "./limits.ts";
|
|
15
22
|
|
|
16
23
|
const ROOT = `${import.meta.dir}/../..`;
|
|
24
|
+
const BUDGETS_JSON_PATH = resolve(ROOT, "budgets.json");
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Deps measured separately from OKE code in export samples.
|
|
28
|
+
* Edge/client absolute gates keep the historical smaller external list.
|
|
29
|
+
*/
|
|
30
|
+
const BUILD_EXTERNALS = ["@duckdb/node-api", "@duckdb/*"] as const;
|
|
31
|
+
|
|
32
|
+
/** Heavy optional / peer packages — not counted in export:* gzip samples. */
|
|
33
|
+
const EXPORT_BUILD_EXTERNALS = [
|
|
34
|
+
...BUILD_EXTERNALS,
|
|
35
|
+
"age-encryption",
|
|
36
|
+
"sently",
|
|
37
|
+
"sently/*",
|
|
38
|
+
"ajv",
|
|
39
|
+
"ajv/*",
|
|
40
|
+
"ajv-formats",
|
|
41
|
+
"oxc-parser",
|
|
42
|
+
"zod",
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
/** How a sample is gated. */
|
|
46
|
+
export type BudgetGate = "absolute" | "regression";
|
|
47
|
+
|
|
48
|
+
/** Report section headings. */
|
|
49
|
+
const GROUP_HEADINGS: Readonly<Record<BudgetGroup, string>> = {
|
|
50
|
+
core: "Core",
|
|
51
|
+
exports: "Exports",
|
|
52
|
+
drivers: "Drivers",
|
|
53
|
+
};
|
|
17
54
|
|
|
18
55
|
/** One measured budget sample. */
|
|
19
56
|
export interface BudgetSample {
|
|
@@ -23,11 +60,15 @@ export interface BudgetSample {
|
|
|
23
60
|
readonly label: string;
|
|
24
61
|
/** Measured value. */
|
|
25
62
|
readonly value: number;
|
|
26
|
-
/**
|
|
63
|
+
/** Upper bound used for `ok` (absolute cap or regression ceiling). */
|
|
27
64
|
readonly limit: number;
|
|
28
65
|
/** Unit for display / snapshot. */
|
|
29
66
|
readonly unit: "bytes" | "ms";
|
|
30
|
-
/**
|
|
67
|
+
/** Gate mode. */
|
|
68
|
+
readonly gate: BudgetGate;
|
|
69
|
+
/** Report section. */
|
|
70
|
+
readonly group: BudgetGroup;
|
|
71
|
+
/** True when under the gate's limit (or first baseline for regression). */
|
|
31
72
|
readonly ok: boolean;
|
|
32
73
|
}
|
|
33
74
|
|
|
@@ -41,18 +82,69 @@ export interface BudgetsSnapshot {
|
|
|
41
82
|
readonly budgets: readonly BudgetSample[];
|
|
42
83
|
}
|
|
43
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Gzip size of all JS artifacts from a Bun.build result.
|
|
87
|
+
*
|
|
88
|
+
* @param result - Successful build
|
|
89
|
+
* @param label - Error context
|
|
90
|
+
*/
|
|
91
|
+
async function gzipBuildOutputs(
|
|
92
|
+
result: Awaited<ReturnType<typeof Bun.build>>,
|
|
93
|
+
label: string,
|
|
94
|
+
): Promise<number> {
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`${label} build failed:\n${result.logs.map(String).join("\n")}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
let total = 0;
|
|
101
|
+
for (const artifact of result.outputs) {
|
|
102
|
+
const path = artifact.path;
|
|
103
|
+
if (path && !/\.(m?js|cjs)$/.test(path) && artifact.kind === "asset") {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const raw = await artifact.arrayBuffer();
|
|
107
|
+
if (raw.byteLength === 0) continue;
|
|
108
|
+
total += Bun.gzipSync(new Uint8Array(raw)).byteLength;
|
|
109
|
+
}
|
|
110
|
+
if (total <= 0) throw new Error(`${label} build produced no JS output`);
|
|
111
|
+
return total;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Gzip size of a minified ESM bundle for one entry file.
|
|
116
|
+
*
|
|
117
|
+
* @param entry - Absolute path to the TypeScript entry
|
|
118
|
+
* @param label - Error context
|
|
119
|
+
* @param external - Packages left external (not inlined)
|
|
120
|
+
*/
|
|
121
|
+
export async function measureEntryGzipBytes(
|
|
122
|
+
entry: string,
|
|
123
|
+
label = entry,
|
|
124
|
+
external: readonly string[] = BUILD_EXTERNALS,
|
|
125
|
+
): Promise<number> {
|
|
126
|
+
const result = await Bun.build({
|
|
127
|
+
entrypoints: [entry],
|
|
128
|
+
minify: true,
|
|
129
|
+
target: "bun",
|
|
130
|
+
format: "esm",
|
|
131
|
+
external: [...external],
|
|
132
|
+
});
|
|
133
|
+
return gzipBuildOutputs(result, label);
|
|
134
|
+
}
|
|
135
|
+
|
|
44
136
|
/**
|
|
45
137
|
* Gzip size of the kernel edge profile bundle.
|
|
46
138
|
*/
|
|
47
139
|
export async function measureKernelEdgeGzipBytes(): Promise<number> {
|
|
48
140
|
const entry = `${import.meta.dir}/../kernel/budget-entry.ts`;
|
|
141
|
+
// Browser target matches the historical edge-profile gate.
|
|
49
142
|
const result = await Bun.build({
|
|
50
143
|
entrypoints: [entry],
|
|
51
144
|
minify: true,
|
|
52
145
|
target: "browser",
|
|
53
146
|
format: "esm",
|
|
54
|
-
|
|
55
|
-
external: ["@duckdb/node-api", "@duckdb/*"],
|
|
147
|
+
external: [...BUILD_EXTERNALS],
|
|
56
148
|
});
|
|
57
149
|
if (!result.success) {
|
|
58
150
|
throw new Error(
|
|
@@ -87,6 +179,33 @@ export async function measureClientGzipBytes(): Promise<number> {
|
|
|
87
179
|
return Bun.gzipSync(new Uint8Array(raw)).byteLength;
|
|
88
180
|
}
|
|
89
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Gzip size of one published package export entry.
|
|
184
|
+
*
|
|
185
|
+
* Bundles through a temporary `import *` anchor so minify cannot drop the
|
|
186
|
+
* re-export graph (direct entry bundles collapse barrels to ~empty stubs).
|
|
187
|
+
*
|
|
188
|
+
* @param entry - Absolute path to the export entry
|
|
189
|
+
*/
|
|
190
|
+
export async function measureExportGzipBytes(entry: string): Promise<number> {
|
|
191
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-export-budget-"));
|
|
192
|
+
const anchor = join(dir, "anchor.ts");
|
|
193
|
+
await Bun.write(
|
|
194
|
+
anchor,
|
|
195
|
+
`import * as mod from ${JSON.stringify(entry)};\n` +
|
|
196
|
+
`export function __okeExportBudgetAnchor(){return mod;}\n`,
|
|
197
|
+
);
|
|
198
|
+
try {
|
|
199
|
+
return await measureEntryGzipBytes(
|
|
200
|
+
anchor,
|
|
201
|
+
`export ${entry}`,
|
|
202
|
+
EXPORT_BUILD_EXTERNALS,
|
|
203
|
+
);
|
|
204
|
+
} finally {
|
|
205
|
+
await rm(dir, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
90
209
|
/** One Console asset's gzip size. */
|
|
91
210
|
export interface ConsoleAssetGzip {
|
|
92
211
|
/** File name under `dist/` or `dist/assets/`. */
|
|
@@ -253,12 +372,49 @@ export function measureRoutingP99Ms(): number {
|
|
|
253
372
|
}
|
|
254
373
|
|
|
255
374
|
/**
|
|
256
|
-
*
|
|
375
|
+
* Regression ceiling from a previous byte sample.
|
|
376
|
+
*
|
|
377
|
+
* @param previous - Last committed value
|
|
378
|
+
*/
|
|
379
|
+
export function exportRegressionLimitBytes(previous: number): number {
|
|
380
|
+
const allowed = Math.max(
|
|
381
|
+
EXPORT_REGRESSION_TOLERANCE_FLOOR_BYTES,
|
|
382
|
+
Math.ceil(previous * EXPORT_REGRESSION_TOLERANCE_RATIO),
|
|
383
|
+
);
|
|
384
|
+
return previous + allowed;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Load previous sample values from a budgets snapshot file (if present).
|
|
389
|
+
*
|
|
390
|
+
* @param path - Absolute path to budgets.json
|
|
391
|
+
*/
|
|
392
|
+
export async function loadPreviousBudgetValues(
|
|
393
|
+
path = BUDGETS_JSON_PATH,
|
|
394
|
+
): Promise<ReadonlyMap<string, number>> {
|
|
395
|
+
const file = Bun.file(path);
|
|
396
|
+
if (!(await file.exists())) return new Map();
|
|
397
|
+
try {
|
|
398
|
+
const snap = (await file.json()) as BudgetsSnapshot;
|
|
399
|
+
const map = new Map<string, number>();
|
|
400
|
+
for (const b of snap.budgets ?? []) {
|
|
401
|
+
map.set(b.id, b.value);
|
|
402
|
+
}
|
|
403
|
+
return map;
|
|
404
|
+
} catch {
|
|
405
|
+
return new Map();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Measure every published budget (absolute AGENTS caps + export regressions).
|
|
257
411
|
*/
|
|
258
412
|
export async function measureAllBudgets(): Promise<BudgetsSnapshot> {
|
|
259
413
|
const pkg = (await Bun.file(`${ROOT}/package.json`).json()) as {
|
|
260
414
|
version: string;
|
|
261
415
|
};
|
|
416
|
+
const previous = await loadPreviousBudgetValues();
|
|
417
|
+
|
|
262
418
|
// Cold start alone first — parallel gzip work contends for CPU on CI and
|
|
263
419
|
// falsely inflates the wall-clock probe.
|
|
264
420
|
const coldStartMedianMs = await measureColdStartMedianMs();
|
|
@@ -277,6 +433,8 @@ export async function measureAllBudgets(): Promise<BudgetsSnapshot> {
|
|
|
277
433
|
kernelEdgeGzipBytes,
|
|
278
434
|
KERNEL_EDGE_BUDGET_BYTES,
|
|
279
435
|
"bytes",
|
|
436
|
+
"absolute",
|
|
437
|
+
"core",
|
|
280
438
|
),
|
|
281
439
|
sample(
|
|
282
440
|
"clientGzipBytes",
|
|
@@ -284,6 +442,8 @@ export async function measureAllBudgets(): Promise<BudgetsSnapshot> {
|
|
|
284
442
|
clientGzipBytes,
|
|
285
443
|
CLIENT_BUDGET_BYTES,
|
|
286
444
|
"bytes",
|
|
445
|
+
"absolute",
|
|
446
|
+
"core",
|
|
287
447
|
),
|
|
288
448
|
sample(
|
|
289
449
|
"consoleInitialGzipBytes",
|
|
@@ -291,6 +451,8 @@ export async function measureAllBudgets(): Promise<BudgetsSnapshot> {
|
|
|
291
451
|
consoleInitialGzipBytes,
|
|
292
452
|
CONSOLE_BUDGET_BYTES,
|
|
293
453
|
"bytes",
|
|
454
|
+
"absolute",
|
|
455
|
+
"core",
|
|
294
456
|
),
|
|
295
457
|
sample(
|
|
296
458
|
"coldStartMedianMs",
|
|
@@ -298,6 +460,8 @@ export async function measureAllBudgets(): Promise<BudgetsSnapshot> {
|
|
|
298
460
|
coldStartMedianMs,
|
|
299
461
|
COLD_START_BUDGET_MS,
|
|
300
462
|
"ms",
|
|
463
|
+
"absolute",
|
|
464
|
+
"core",
|
|
301
465
|
),
|
|
302
466
|
sample(
|
|
303
467
|
"routingP99Ms",
|
|
@@ -305,9 +469,30 @@ export async function measureAllBudgets(): Promise<BudgetsSnapshot> {
|
|
|
305
469
|
routingP99Ms,
|
|
306
470
|
ROUTING_P99_BUDGET_MS,
|
|
307
471
|
"ms",
|
|
472
|
+
"absolute",
|
|
473
|
+
"core",
|
|
308
474
|
),
|
|
309
475
|
];
|
|
310
476
|
|
|
477
|
+
const exportTargets = await resolveExportBudgetTargets();
|
|
478
|
+
const exportSizes = await Promise.all(
|
|
479
|
+
exportTargets.map(async (t) => ({
|
|
480
|
+
target: t,
|
|
481
|
+
bytes: await measureExportGzipBytes(t.entry),
|
|
482
|
+
})),
|
|
483
|
+
);
|
|
484
|
+
for (const { target, bytes } of exportSizes) {
|
|
485
|
+
budgets.push(
|
|
486
|
+
regressionSample(
|
|
487
|
+
target.id,
|
|
488
|
+
target.label,
|
|
489
|
+
target.group,
|
|
490
|
+
bytes,
|
|
491
|
+
previous,
|
|
492
|
+
),
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
311
496
|
return {
|
|
312
497
|
measuredAt: new Date().toISOString(),
|
|
313
498
|
version: pkg.version,
|
|
@@ -316,7 +501,7 @@ export async function measureAllBudgets(): Promise<BudgetsSnapshot> {
|
|
|
316
501
|
}
|
|
317
502
|
|
|
318
503
|
/**
|
|
319
|
-
* Format a snapshot for CI logs.
|
|
504
|
+
* Format a snapshot for CI logs (Core / Exports / Drivers sections).
|
|
320
505
|
*
|
|
321
506
|
* @param snapshot - Measured budgets
|
|
322
507
|
*/
|
|
@@ -324,11 +509,54 @@ export function formatBudgetsReport(snapshot: BudgetsSnapshot): string {
|
|
|
324
509
|
const lines = [
|
|
325
510
|
`okengine budgets v${snapshot.version} @ ${snapshot.measuredAt}`,
|
|
326
511
|
];
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
);
|
|
512
|
+
const order: readonly BudgetGroup[] = ["core", "exports", "drivers"];
|
|
513
|
+
for (const group of order) {
|
|
514
|
+
const rows = snapshot.budgets.filter((b) => b.group === group);
|
|
515
|
+
if (rows.length === 0) continue;
|
|
516
|
+
lines.push("");
|
|
517
|
+
lines.push(GROUP_HEADINGS[group]);
|
|
518
|
+
for (const b of rows) {
|
|
519
|
+
const flag = b.ok ? "ok" : "FAIL";
|
|
520
|
+
lines.push(
|
|
521
|
+
` [${flag}] ${b.label}: ${formatValue(b.value, b.unit)} < ${formatValue(b.limit, b.unit)}`,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return `${lines.join("\n")}\n`;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Full markdown document for [`BUDGETS.md`](../../BUDGETS.md).
|
|
530
|
+
* Written by `bun run budgets` — do not edit the tables by hand.
|
|
531
|
+
*
|
|
532
|
+
* @param snapshot - Measured budgets
|
|
533
|
+
*/
|
|
534
|
+
export function formatBudgetsMarkdown(snapshot: BudgetsSnapshot): string {
|
|
535
|
+
const lines = [
|
|
536
|
+
"# Budgets",
|
|
537
|
+
"",
|
|
538
|
+
"Published numbers from [`budgets.json`](budgets.json). Refresh with `bun run budgets`.",
|
|
539
|
+
"",
|
|
540
|
+
`_okengine v${snapshot.version} · measured ${snapshot.measuredAt}_`,
|
|
541
|
+
"",
|
|
542
|
+
"Core rows are absolute AGENTS caps. Exports and Drivers fail on regression vs the prior [`budgets.json`](budgets.json) (max +256 B or +2%). Export gzip excludes peers/optionals (`zod`, `age-encryption`, `sently`, `oxc-parser`, `ajv`).",
|
|
543
|
+
];
|
|
544
|
+
const order: readonly BudgetGroup[] = ["core", "exports", "drivers"];
|
|
545
|
+
for (const group of order) {
|
|
546
|
+
const rows = snapshot.budgets.filter((b) => b.group === group);
|
|
547
|
+
if (rows.length === 0) continue;
|
|
548
|
+
const limitCol = group === "core" ? "Limit" : "Ceiling";
|
|
549
|
+
lines.push("");
|
|
550
|
+
lines.push(`## ${GROUP_HEADINGS[group]}`);
|
|
551
|
+
lines.push("");
|
|
552
|
+
lines.push(`| | Measured | ${limitCol} |`);
|
|
553
|
+
lines.push("|---|---|---|");
|
|
554
|
+
for (const b of rows) {
|
|
555
|
+
const mark = b.ok ? "" : " **FAIL**";
|
|
556
|
+
lines.push(
|
|
557
|
+
`| ${b.label}${mark} | ${formatValue(b.value, b.unit)} | ${formatValue(b.limit, b.unit)} |`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
332
560
|
}
|
|
333
561
|
return `${lines.join("\n")}\n`;
|
|
334
562
|
}
|
|
@@ -348,10 +576,65 @@ function sample(
|
|
|
348
576
|
value: number,
|
|
349
577
|
limit: number,
|
|
350
578
|
unit: BudgetSample["unit"],
|
|
579
|
+
gate: BudgetGate,
|
|
580
|
+
group: BudgetGroup,
|
|
351
581
|
): BudgetSample {
|
|
352
582
|
const rounded =
|
|
353
583
|
unit === "bytes" ? Math.round(value) : Math.round(value * 1000) / 1000;
|
|
354
|
-
return {
|
|
584
|
+
return {
|
|
585
|
+
id,
|
|
586
|
+
label,
|
|
587
|
+
value: rounded,
|
|
588
|
+
limit,
|
|
589
|
+
unit,
|
|
590
|
+
gate,
|
|
591
|
+
group,
|
|
592
|
+
ok: rounded < limit,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Byte sample gated against the previous committed value (+ tolerance).
|
|
598
|
+
* First appearance records a baseline (`ok: true`, `limit` = value + floor).
|
|
599
|
+
*
|
|
600
|
+
* @param id - Sample id
|
|
601
|
+
* @param label - Human label
|
|
602
|
+
* @param group - Report section
|
|
603
|
+
* @param value - Measured gzip bytes
|
|
604
|
+
* @param previous - Prior snapshot values by id
|
|
605
|
+
*/
|
|
606
|
+
function regressionSample(
|
|
607
|
+
id: string,
|
|
608
|
+
label: string,
|
|
609
|
+
group: BudgetGroup,
|
|
610
|
+
value: number,
|
|
611
|
+
previous: ReadonlyMap<string, number>,
|
|
612
|
+
): BudgetSample {
|
|
613
|
+
const rounded = Math.round(value);
|
|
614
|
+
const prior = previous.get(id);
|
|
615
|
+
if (prior === undefined) {
|
|
616
|
+
return {
|
|
617
|
+
id,
|
|
618
|
+
label,
|
|
619
|
+
value: rounded,
|
|
620
|
+
limit: rounded + EXPORT_REGRESSION_TOLERANCE_FLOOR_BYTES,
|
|
621
|
+
unit: "bytes",
|
|
622
|
+
gate: "regression",
|
|
623
|
+
group,
|
|
624
|
+
ok: true,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
const limit = exportRegressionLimitBytes(prior);
|
|
628
|
+
return {
|
|
629
|
+
id,
|
|
630
|
+
label,
|
|
631
|
+
value: rounded,
|
|
632
|
+
limit,
|
|
633
|
+
unit: "bytes",
|
|
634
|
+
gate: "regression",
|
|
635
|
+
group,
|
|
636
|
+
ok: rounded < limit,
|
|
637
|
+
};
|
|
355
638
|
}
|
|
356
639
|
|
|
357
640
|
function formatValue(value: number, unit: BudgetSample["unit"]): string {
|
package/src/release/publish.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* Measure every published budget, print the report, write `budgets.json
|
|
3
|
+
* Measure every published budget, print the report, write `budgets.json`
|
|
4
|
+
* and sync `BUDGETS.md`.
|
|
4
5
|
*
|
|
5
6
|
* Exit 1 on any regression. CI runs this as a gate; the snapshot is written
|
|
6
7
|
* locally and committed with releases — not auto-pushed from Actions.
|
|
@@ -9,26 +10,32 @@
|
|
|
9
10
|
import { resolve } from "node:path";
|
|
10
11
|
import {
|
|
11
12
|
budgetsPass,
|
|
13
|
+
formatBudgetsMarkdown,
|
|
12
14
|
formatBudgetsReport,
|
|
13
15
|
measureAllBudgets,
|
|
14
16
|
type BudgetsSnapshot,
|
|
15
17
|
} from "./measure.ts";
|
|
18
|
+
import { BUDGETS_MD } from "./readme.ts";
|
|
16
19
|
|
|
17
20
|
const ROOT = resolve(import.meta.dir, "../..");
|
|
18
21
|
/** Snapshot path at the repository root. */
|
|
19
22
|
export const BUDGETS_JSON = resolve(ROOT, "budgets.json");
|
|
23
|
+
/** Generated markdown report (`BUDGETS.md`). */
|
|
24
|
+
export const BUDGETS_MARKDOWN = BUDGETS_MD;
|
|
20
25
|
|
|
21
26
|
/** Options for {@link publishBudgets}. */
|
|
22
27
|
export interface PublishBudgetsOptions {
|
|
23
28
|
/** Destination path (defaults to repo-root `budgets.json`). */
|
|
24
29
|
readonly outPath?: string;
|
|
25
|
-
/**
|
|
30
|
+
/** Markdown report path (defaults to repo-root `BUDGETS.md`). */
|
|
31
|
+
readonly markdownPath?: string;
|
|
32
|
+
/** Skip writing the snapshot and markdown report. */
|
|
26
33
|
readonly dryRun?: boolean;
|
|
27
34
|
readonly write?: (text: string) => void;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
/**
|
|
31
|
-
* Measure, print, and optionally persist the budgets snapshot.
|
|
38
|
+
* Measure, print, and optionally persist the budgets snapshot + markdown.
|
|
32
39
|
*
|
|
33
40
|
* @param options - Output / dry-run
|
|
34
41
|
* @returns Exit code (1 when any budget fails)
|
|
@@ -44,6 +51,10 @@ export async function publishBudgets(
|
|
|
44
51
|
const path = options.outPath ?? BUDGETS_JSON;
|
|
45
52
|
await Bun.write(path, `${JSON.stringify(snapshot, null, 2)}\n`);
|
|
46
53
|
write(`wrote ${path}\n`);
|
|
54
|
+
|
|
55
|
+
const markdownPath = options.markdownPath ?? BUDGETS_MARKDOWN;
|
|
56
|
+
await Bun.write(markdownPath, formatBudgetsMarkdown(snapshot));
|
|
57
|
+
write(`wrote ${markdownPath}\n`);
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
return { code: budgetsPass(snapshot) ? 0 : 1, snapshot };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated budgets markdown document.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { formatBudgetsMarkdown, type BudgetsSnapshot } from "./measure.ts";
|
|
7
|
+
import { BUDGETS_MD } from "./readme.ts";
|
|
8
|
+
|
|
9
|
+
describe("budgets markdown", () => {
|
|
10
|
+
test("BUDGETS_MD points at root BUDGETS.md", () => {
|
|
11
|
+
expect(BUDGETS_MD.endsWith("/BUDGETS.md")).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("formatBudgetsMarkdown is a standalone document with short names", () => {
|
|
15
|
+
const snapshot: BudgetsSnapshot = {
|
|
16
|
+
measuredAt: "2026-07-25T00:00:00.000Z",
|
|
17
|
+
version: "0.1.6",
|
|
18
|
+
budgets: [
|
|
19
|
+
{
|
|
20
|
+
id: "kernelEdgeGzipBytes",
|
|
21
|
+
label: "Kernel (edge profile)",
|
|
22
|
+
value: 8969,
|
|
23
|
+
limit: 15360,
|
|
24
|
+
unit: "bytes",
|
|
25
|
+
gate: "absolute",
|
|
26
|
+
group: "core",
|
|
27
|
+
ok: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "export:./channel",
|
|
31
|
+
label: "channel",
|
|
32
|
+
value: 6144,
|
|
33
|
+
limit: 6400,
|
|
34
|
+
unit: "bytes",
|
|
35
|
+
gate: "regression",
|
|
36
|
+
group: "exports",
|
|
37
|
+
ok: true,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "export:./drivers/postgres",
|
|
41
|
+
label: "postgres",
|
|
42
|
+
value: 1413,
|
|
43
|
+
limit: 1669,
|
|
44
|
+
unit: "bytes",
|
|
45
|
+
gate: "regression",
|
|
46
|
+
group: "drivers",
|
|
47
|
+
ok: true,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
const md = formatBudgetsMarkdown(snapshot);
|
|
52
|
+
expect(md.startsWith("# Budgets\n")).toBe(true);
|
|
53
|
+
expect(md).toContain("## Core");
|
|
54
|
+
expect(md).toContain("## Exports");
|
|
55
|
+
expect(md).toContain("## Drivers");
|
|
56
|
+
expect(md).toContain("| channel |");
|
|
57
|
+
expect(md).toContain("| postgres |");
|
|
58
|
+
expect(md).toContain("[`budgets.json`](budgets.json)");
|
|
59
|
+
expect(md).not.toContain("./channel");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path helpers for the generated budgets markdown document.
|
|
3
|
+
*
|
|
4
|
+
* The tables live in `BUDGETS.md` at the repo root (rewritten by
|
|
5
|
+
* `bun run budgets`). README only links there.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
|
|
10
|
+
const ROOT = resolve(import.meta.dir, "../..");
|
|
11
|
+
|
|
12
|
+
/** Generated budgets report (markdown tables). */
|
|
13
|
+
export const BUDGETS_MD = resolve(ROOT, "BUDGETS.md");
|
package/src/runtime/index.ts
CHANGED
|
@@ -123,7 +123,7 @@ describe("secureFetch", () => {
|
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
describe("Host/Origin entry points (6530 / 6533 / 6535)", () => {
|
|
126
|
+
describe("Host/Origin entry points (6530 / 6533 / 6535 / 6536)", () => {
|
|
127
127
|
test("every HTTP surface routes through checkRequestSecurity", async () => {
|
|
128
128
|
/**
|
|
129
129
|
* Entry-point map (source of truth for §10.1):
|
|
@@ -132,6 +132,7 @@ describe("Host/Origin entry points (6530 / 6533 / 6535)", () => {
|
|
|
132
132
|
* and checkRequestSecurity before WebSocket upgrade on /console/live
|
|
133
133
|
* - MCP :6535 → mcp/server.ts → checkRequestSecurity (same primitive;
|
|
134
134
|
* secureFetch is a thin wrapper around it)
|
|
135
|
+
* - Docs MCP :6536 → mcp/docs-server.ts → checkRequestSecurity
|
|
135
136
|
*/
|
|
136
137
|
const surfaces: ReadonlyArray<{
|
|
137
138
|
readonly file: string;
|
|
@@ -153,6 +154,10 @@ describe("Host/Origin entry points (6530 / 6533 / 6535)", () => {
|
|
|
153
154
|
file: `${import.meta.dir}/../mcp/server.ts`,
|
|
154
155
|
mustInclude: ["checkRequestSecurity", "forbiddenResponse"],
|
|
155
156
|
},
|
|
157
|
+
{
|
|
158
|
+
file: `${import.meta.dir}/../mcp/docs-server.ts`,
|
|
159
|
+
mustInclude: ["checkRequestSecurity", "forbiddenResponse"],
|
|
160
|
+
},
|
|
156
161
|
];
|
|
157
162
|
|
|
158
163
|
for (const surface of surfaces) {
|
package/src/runtime/types.ts
CHANGED
|
@@ -17,6 +17,12 @@ export const CONSOLE_PORT = 6533;
|
|
|
17
17
|
/** Default MCP listen port (O·K·E mnemonic). */
|
|
18
18
|
export const MCP_PORT = 6535;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Default docs MCP listen port (read-only documentation search/fetch).
|
|
22
|
+
* Distinct from {@link MCP_PORT} — serves docs content, not a live Manifest.
|
|
23
|
+
*/
|
|
24
|
+
export const DOCS_MCP_PORT = 6536;
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* Minimal app surface a runtime can serve.
|
|
22
28
|
* Satisfied by {@link import("../kernel/app.ts").OkeApp}.
|
package/src/term.test.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal chrome — color off stays plain; claim/banner keep structure.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import {
|
|
7
|
+
formatAppReadyLine,
|
|
8
|
+
formatClaimNote,
|
|
9
|
+
formatDevBanner,
|
|
10
|
+
formatServiceLine,
|
|
11
|
+
formatStatusLine,
|
|
12
|
+
termStyle,
|
|
13
|
+
} from "./term.ts";
|
|
14
|
+
|
|
15
|
+
describe("term", () => {
|
|
16
|
+
test("termStyle(false) strips ANSI", () => {
|
|
17
|
+
const s = termStyle(false);
|
|
18
|
+
expect(s.cyan).toBe("");
|
|
19
|
+
expect(s.bold).toBe("");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("formatDevBanner is readable without color", () => {
|
|
23
|
+
const out = formatDevBanner({ color: false });
|
|
24
|
+
expect(out).toContain("oke dev");
|
|
25
|
+
expect(out).toContain("Starting");
|
|
26
|
+
expect(out).toContain("watching");
|
|
27
|
+
expect(out).not.toMatch(/\u001b\[/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("formatServiceLine / formatAppReadyLine include URL", () => {
|
|
31
|
+
expect(formatServiceLine("Console", "http://127.0.0.1:6533", false)).toContain(
|
|
32
|
+
"http://127.0.0.1:6533",
|
|
33
|
+
);
|
|
34
|
+
expect(formatAppReadyLine("http://127.0.0.1:6530", false)).toContain(
|
|
35
|
+
"http://127.0.0.1:6530",
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("formatClaimNote embeds code and ownership line", () => {
|
|
40
|
+
const code = "aabbccddeeff00112233445566778899";
|
|
41
|
+
const out = formatClaimNote(code, false);
|
|
42
|
+
expect(out).toContain(code);
|
|
43
|
+
expect(out).toContain("Claim code");
|
|
44
|
+
expect(out).toContain("owns the server");
|
|
45
|
+
expect(out).not.toMatch(/\u001b\[/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("formatStatusLine keeps message", () => {
|
|
49
|
+
expect(formatStatusLine("stack up (sql)", false)).toContain("stack up");
|
|
50
|
+
});
|
|
51
|
+
});
|