geospec 0.1.0-beta.7 → 0.1.0-beta.8
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/agent/geospec-authoring/SKILL.md +11 -11
- package/agent/geospec-authoring/api-functions.md +1 -16
- package/agent/geospec-authoring/api-index.md +50 -46
- package/agent/geospec-authoring/api-types.md +188 -168
- package/agent/geospec-authoring/public-api-functions.md +29 -22
- package/agent/geospec-authoring/public-api-index.md +196 -183
- package/agent/geospec-authoring/public-api-types-2.md +188 -220
- package/agent/geospec-authoring/public-api-types-3.md +112 -99
- package/agent/geospec-authoring/public-api-types.md +236 -125
- package/agent/resources.js +12 -12
- package/agent/skills.json +2 -2
- package/dist/config/index.d.mts +1 -2
- package/dist/config/index.mjs +1 -1
- package/dist/config/types.d.mts +0 -3
- package/dist/config/validate.mjs +1 -1
- package/dist/mesh/index.d.mts +2 -2
- package/dist/model/index.d.mts +3 -3
- package/dist/model/index.mjs +1 -1
- package/dist/model/mesh-evidence-matchers.d.mts +12 -0
- package/dist/model/mesh-evidence-matchers.mjs +1 -0
- package/dist/model/native-model-loader.d.mts +8 -8
- package/dist/model/native-model-loader.mjs +1 -1
- package/dist/model/node/command-exporter.d.mts +31 -0
- package/dist/model/node/command-exporter.mjs +1 -0
- package/dist/model/node/index.d.mts +2 -0
- package/dist/model/node/index.mjs +1 -0
- package/dist/model/types.d.mts +111 -63
- package/dist/runner/collector.d.mts +0 -2
- package/dist/runner/node/index.d.mts +2 -0
- package/dist/runner/node/index.mjs +1 -0
- package/dist/runner/node/node-vm-filesystem.d.mts +22 -0
- package/dist/runner/node/node-vm-filesystem.mjs +1 -0
- package/dist/runner/pool/events.mjs +1 -0
- package/dist/runner/pool/pool-messages.d.mts +0 -10
- package/dist/runner/pool/pool.d.mts +36 -0
- package/dist/runner/pool/pool.mjs +1 -0
- package/dist/runner/pool/shard-planner.mjs +1 -0
- package/dist/runner/pool/shard-timings.d.mts +36 -0
- package/dist/runner/pool/transport.mjs +1 -0
- package/dist/runner/pool/worker-host.d.mts +36 -0
- package/dist/runner/pool/worker-host.mjs +1 -0
- package/dist/runner/run-geospec-module.mjs +1 -1
- package/dist/runner/types.d.mts +0 -4
- package/dist/runner/worker/index.d.mts +5 -2
- package/dist/runner/worker/index.mjs +1 -1
- package/dist/runner/worker/runner-types.d.mts +2 -14
- package/dist/runner/worker/serial-runner.mjs +1 -1
- package/package.json +14 -4
- package/dist/config/tau-project-artifact.d.mts +0 -54
- package/dist/config/tau-project-artifact.mjs +0 -3
- package/dist/model/export-intent.d.mts +0 -79
- package/dist/model/export-intent.mjs +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{GeoSpecModelLoadError as e}from"../errors.mjs";import{z as t}from"zod";import{mkdtemp as n,readFile as r,rm as i}from"node:fs/promises";import{isAbsolute as a,join as o,relative as s,resolve as c,sep as l}from"node:path";import{spawn as u}from"node:child_process";import{tmpdir as d}from"node:os";import f from"node:process";const p=t.object({protocol:t.literal(1),error:t.object({code:t.string().min(1),message:t.string(),suggestion:t.string().optional()})}),m=t.object({protocol:t.literal(1),format:t.enum([`glb`,`step`]),sourceUnit:t.string().optional(),primary:t.string().min(1),resources:t.array(t.object({name:t.string().min(1),path:t.string().min(1)})).optional(),consumedSources:t.array(t.object({path:t.string().min(1),sha256:t.string().min(1)}))}),h=({id:t,file:n,reason:r,details:i={}})=>new e([{code:`GEOSPEC_EXPORTER_FAILED`,severity:`error`,message:`Exporter '${t}' failed to export '${n}': ${r}`,suggestion:`Run the exporter command by hand with the same arguments and fix what it reports.`,details:{exporter:t,file:n,...i}}]),g=(e,t)=>{let n=c(e,t),r=s(e,n);return r===``||r===`..`||r.startsWith(`..${l}`)||a(r)?void 0:n},_=async e=>{let t=await r(e);return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)},v=(e,t,n)=>[`--file`,e.file,`--format`,e.format,`--project`,t,`--out`,n,...e.parameters===void 0?[]:[`--parameters-json`,JSON.stringify(e.parameters)],...e.mesh?.linearTolerance===void 0?[]:[`--mesh-linear-tolerance`,String(e.mesh.linearTolerance)],...e.mesh?.angularToleranceDegrees===void 0?[]:[`--mesh-angular-tolerance-degrees`,String(e.mesh.angularToleranceDegrees)],...e.region===void 0?[]:[`--region-min`,e.region.min.join(`,`),`--region-max`,e.region.max.join(`,`)]],y=e=>{let t=new TextDecoder;return e.map(e=>t.decode(e,{stream:!0})).join(``)+t.decode()},b=async(e,t,n)=>new Promise((r,i)=>{let a=u(e,t,{cwd:n.cwd,env:n.env,stdio:[`ignore`,`pipe`,`pipe`],...n.signal===void 0?{}:{signal:n.signal}}),o=[],s=[];a.stdout.on(`data`,e=>o.push(e)),a.stderr.on(`data`,e=>s.push(e)),a.once(`error`,i),a.once(`close`,e=>{r({exitCode:e??void 0,stdout:y(o),stderr:y(s)})})}),x=t=>{let[r,...a]=t.command;if(t.id===``||t.extensions.length===0||r===void 0||r===``)throw TypeError(`commandExporter needs an id, at least one extension and a command.`);if(t.capabilities.formats.length===0)throw TypeError(`commandExporter '${t.id}' must declare at least one format.`);if(t.capabilities.inlineSources===!0)throw TypeError(`commandExporter '${t.id}' cannot accept inline sources: protocol 1 passes files only.`);let{id:s}=t;return{id:s,extensions:[...t.extensions],capabilities:{...t.capabilities,formats:[...t.capabilities.formats]},async export(c){let l=c.projectPath??t.cwd??f.cwd(),u=await n(o(d(),`geospec-export-`));try{let n;try{n=await b(r,[...a,...v(c,l,u)],{cwd:t.cwd??l,env:{...f.env,...t.env},signal:c.signal})}catch(e){throw h({id:s,file:c.file,reason:`the command could not run (${e instanceof Error?e.message:String(e)}).`})}let i=n.stderr.slice(-2e3),o;try{o=JSON.parse(n.stdout)}catch{throw h({id:s,file:c.file,reason:`it printed no protocol 1 JSON document (exit ${String(n.exitCode)}).`,details:{exitCode:n.exitCode,stderr:i}})}let d=p.safeParse(o);if(d.success){let{code:t,message:n,suggestion:r}=d.data.error;throw new e([{code:t,severity:`error`,message:n,...r===void 0?{}:{suggestion:r},details:{exporter:s,file:c.file}}])}let y=m.safeParse(o);if(n.exitCode!==0||!y.success)throw h({id:s,file:c.file,reason:n.exitCode===0?`its JSON document is not a protocol 1 response.`:`it exited ${String(n.exitCode)} without a protocol 1 error.`,details:{exitCode:n.exitCode,stderr:i}});let x=y.data,S=g(u,x.primary),C=(x.resources??[]).flatMap(({name:e,path:t})=>{let n=g(u,t);return n===void 0?[]:[{name:e,path:n}]});if(S===void 0||C.length!==(x.resources??[]).length)throw h({id:s,file:c.file,reason:`it named an artifact outside its output directory.`});let[w,T]=await Promise.all([_(S),Promise.all(C.map(async({name:e,path:t})=>({name:e,bytes:await _(t)})))]);return{bytes:w,format:x.format,...x.sourceUnit===void 0?{}:{sourceUnit:x.sourceUnit},...T.length===0?{}:{resources:T},consumedSources:x.consumedSources}}finally{await i(u,{recursive:!0,force:!0})}}}};export{x as commandExporter};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{commandExporter as e}from"./command-exporter.mjs";export{e as commandExporter};
|
package/dist/model/types.d.mts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { GeoSpecUnit } from "../geometry-unit.mjs";
|
|
2
2
|
import { StepSource, StepStreamingMode } from "../step/types.mjs";
|
|
3
|
-
import { MeshFileFormat } from "../mesh/types.mjs";
|
|
3
|
+
import { GeometryDiagnostic, MeshFileFormat } from "../mesh/types.mjs";
|
|
4
4
|
import { GeoSpecSubject } from "./subject.mjs";
|
|
5
5
|
import { GeoSpecNativeModelEngine, GeoSpecNativeSourceReader } from "./native-model-loader.mjs";
|
|
6
|
-
import { RuntimeClient } from "@taucad/runtime";
|
|
7
6
|
//#region src/model/types.d.ts
|
|
8
7
|
/**
|
|
9
8
|
* In-memory triangle mesh source.
|
|
@@ -29,49 +28,110 @@ type MeshSource = Uint8Array<ArrayBuffer> | ArrayBuffer | Blob | File | URL | st
|
|
|
29
28
|
*/
|
|
30
29
|
type GeoSpecModelFormat = MeshFileFormat | 'step' | 'stp';
|
|
31
30
|
/**
|
|
32
|
-
*
|
|
31
|
+
* Formats a {@link GeoSpecModelExporter} may produce from a source-kind file.
|
|
33
32
|
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `snapshotSource` lets one run reuse an identical file load after confirming
|
|
41
|
-
* that the source closure it exported is unchanged.
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
type GeoSpecExportFormat = Extract<GeoSpecModelFormat, 'glb' | 'step'>;
|
|
36
|
+
/**
|
|
37
|
+
* What a {@link GeoSpecModelExporter} declares it can do. The loader refuses a load the exporter
|
|
38
|
+
* cannot serve with `GEOSPEC_EXPORTER_UNSUPPORTED` before it calls `export`.
|
|
42
39
|
*
|
|
43
40
|
* @public
|
|
44
41
|
*/
|
|
45
|
-
type
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}) => void): () => void;
|
|
42
|
+
type GeoSpecExporterCapabilities = {
|
|
43
|
+
/** The formats `export` produces; at least one. A GLB-only exporter refuses STEP loads up front. */
|
|
44
|
+
readonly formats: readonly GeoSpecExportFormat[];
|
|
45
|
+
/** Accepts `parameters`. Without it, a load that passes parameters refuses. */
|
|
46
|
+
readonly parameters?: boolean;
|
|
47
|
+
/** Crops to `region` (an axis-aligned millimetre box) before export. Without it, a region load refuses. */
|
|
48
|
+
readonly region?: boolean;
|
|
49
|
+
/** Accepts `inline` sources, which back `loadModel({ code })`. Without it, an inline load refuses. */
|
|
50
|
+
readonly inlineSources?: boolean;
|
|
55
51
|
};
|
|
56
52
|
/**
|
|
57
|
-
*
|
|
53
|
+
* One export request. Every field but `inline` and `signal` maps onto subprocess protocol 1, so the
|
|
54
|
+
* same request crosses a process boundary unchanged.
|
|
58
55
|
*
|
|
59
56
|
* @public
|
|
60
57
|
*/
|
|
61
|
-
type
|
|
58
|
+
type GeoSpecModelExportRequest = {
|
|
59
|
+
/** Entry file, as a path from the project root. */
|
|
60
|
+
readonly file: string;
|
|
61
|
+
/** Absolute project root the exporter resolves `file` and its imports against, when the host has one. */
|
|
62
|
+
readonly projectPath?: string;
|
|
63
|
+
readonly format: GeoSpecExportFormat;
|
|
64
|
+
/** JSON-serialisable parameter values; present only when the exporter declares `parameters`. */
|
|
65
|
+
readonly parameters?: Readonly<Record<string, unknown>>;
|
|
66
|
+
/** Tessellation tolerances for a mesh export. */
|
|
67
|
+
readonly mesh?: {
|
|
68
|
+
readonly linearTolerance?: number;
|
|
69
|
+
readonly angularToleranceDegrees?: number;
|
|
70
|
+
};
|
|
71
|
+
/** Millimetre crop box; present only when the exporter declares `region`. */
|
|
72
|
+
readonly region?: GeoSpecLoadRegion;
|
|
73
|
+
/** Source text keyed by project-relative path; present only when the exporter declares `inlineSources`. */
|
|
74
|
+
readonly inline?: Readonly<Record<string, string>>;
|
|
75
|
+
readonly signal?: AbortSignal;
|
|
76
|
+
};
|
|
62
77
|
/**
|
|
63
|
-
*
|
|
64
|
-
* generic Tau runtime preset.
|
|
78
|
+
* The bytes an exporter returns, in the canonical Z-up millimetre frame.
|
|
65
79
|
*
|
|
66
80
|
* @public
|
|
67
81
|
*/
|
|
68
|
-
type
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
type GeoSpecModelArtifact = {
|
|
83
|
+
readonly bytes: Uint8Array<ArrayBuffer>;
|
|
84
|
+
readonly format: GeoSpecExportFormat;
|
|
85
|
+
/** Unit of raw GLB coordinates; defaults to `'mm'`. STEP carries its own unit. */
|
|
86
|
+
readonly sourceUnit?: GeoSpecUnit;
|
|
87
|
+
/** External resources the primary bytes reference, by name. */
|
|
88
|
+
readonly resources?: ReadonlyArray<{
|
|
89
|
+
readonly name: string;
|
|
90
|
+
readonly bytes: Uint8Array<ArrayBuffer>;
|
|
91
|
+
}>;
|
|
92
|
+
/**
|
|
93
|
+
* Every source file the export read, as a path from the project root with its SHA-256 (64 hex digits,
|
|
94
|
+
* optionally prefixed `sha256:`), or `'missing'` for a file the export looked for and did not find. The
|
|
95
|
+
* load records it as the subject's source revision.
|
|
96
|
+
*/
|
|
97
|
+
readonly consumedSources?: GeoSpecConsumedSources;
|
|
98
|
+
/** Notices from a successful export, admitted with the subject so its evidence keeps them. */
|
|
99
|
+
readonly diagnostics?: readonly GeometryDiagnostic[];
|
|
100
|
+
/** The export options the exporter applied, recorded in the load evidence beside the exporter id. */
|
|
101
|
+
readonly exportOptions?: Readonly<Record<string, unknown>>;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The source files one export read, each with its SHA-256 or `'missing'`.
|
|
105
|
+
*
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
108
|
+
type GeoSpecConsumedSources = ReadonlyArray<{
|
|
109
|
+
readonly path: string;
|
|
110
|
+
readonly sha256: string;
|
|
111
|
+
}>;
|
|
112
|
+
/**
|
|
113
|
+
* Turns a source-kind model file into GLB or STEP bytes for GeoSpec. Hosts pass exporters to the
|
|
114
|
+
* model loader; the first whose `extensions` claim a file exports it. `extensions` have no leading dot,
|
|
115
|
+
* and `'*'` claims every file. `export` may be called concurrently; the exporter owns its caching.
|
|
116
|
+
*
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
type GeoSpecModelExporter = {
|
|
120
|
+
readonly id: string;
|
|
121
|
+
readonly extensions: readonly string[];
|
|
122
|
+
readonly capabilities: GeoSpecExporterCapabilities;
|
|
123
|
+
export(request: GeoSpecModelExportRequest): Promise<GeoSpecModelArtifact>;
|
|
124
|
+
/**
|
|
125
|
+
* The sources an export of `file` would read now, without exporting. With it, the loader reuses an
|
|
126
|
+
* earlier identical load in the same scope when every digest still matches; without it, every file
|
|
127
|
+
* load exports afresh.
|
|
128
|
+
*/
|
|
129
|
+
revision?(request: Pick<GeoSpecModelExportRequest, 'file' | 'projectPath'>): Promise<GeoSpecConsumedSources | undefined>;
|
|
130
|
+
/**
|
|
131
|
+
* Releases what the exporter created for one scope. The loader calls it from `releaseAll` for every
|
|
132
|
+
* exporter it exported through in that scope; the exporter stays usable for the next one.
|
|
133
|
+
*/
|
|
134
|
+
close?(): Promise<void>;
|
|
75
135
|
};
|
|
76
136
|
/**
|
|
77
137
|
* Axis-aligned crop box in canonical Z-up millimetres; every `min` axis is below its `max` axis.
|
|
@@ -139,13 +199,9 @@ type LoadModelCodeOptions<Code extends Record<string, string> = Record<string, s
|
|
|
139
199
|
rootDir?: never;
|
|
140
200
|
/** Geometry format to export. Defaults to `glb`. */
|
|
141
201
|
format?: GeoSpecModelFormat;
|
|
142
|
-
/** Explicit parameters passed to the
|
|
202
|
+
/** Explicit parameters passed to the exporter. */
|
|
143
203
|
parameters?: Record<string, unknown>;
|
|
144
|
-
/**
|
|
145
|
-
runtime?: GeoSpecRuntimeClient | GeoSpecRuntimeClientFactory;
|
|
146
|
-
/** Source-specific runtime adapters, e.g. host-provided GPL-isolated kernels. */
|
|
147
|
-
sourceAdapters?: readonly GeoSpecRuntimeSourceAdapter[];
|
|
148
|
-
/** Project root used by runtime integrations. */
|
|
204
|
+
/** Absolute project root passed to the exporter. */
|
|
149
205
|
projectPath?: string;
|
|
150
206
|
/** STEP reader strategy used for STEP exports. */
|
|
151
207
|
stepStreaming?: StepStreamingMode;
|
|
@@ -162,12 +218,11 @@ type LoadModelCodeOptions<Code extends Record<string, string> = Record<string, s
|
|
|
162
218
|
*/
|
|
163
219
|
workUnitLimit?: number;
|
|
164
220
|
/**
|
|
165
|
-
* Axis-aligned millimetre box the
|
|
166
|
-
* can be checked region by region. Only
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* it never stands for the whole model.
|
|
221
|
+
* Axis-aligned millimetre box the exporter crops the model to before export, so a large mesh model
|
|
222
|
+
* can be checked region by region. Only an exporter that declares `region` honours it; any other
|
|
223
|
+
* exporter, or a crop the exporter cannot make, refuses with a typed diagnostic instead of loading
|
|
224
|
+
* the whole model. The cropped subject is a different subject: its load evidence records `region`,
|
|
225
|
+
* so a claim on it never stands for the whole model.
|
|
171
226
|
*/
|
|
172
227
|
region?: GeoSpecLoadRegion;
|
|
173
228
|
};
|
|
@@ -188,16 +243,12 @@ type LoadModelFileOptions = {
|
|
|
188
243
|
* evidence.
|
|
189
244
|
*/
|
|
190
245
|
rootDir?: string;
|
|
191
|
-
/**
|
|
246
|
+
/** Absolute project root passed to the exporter. */
|
|
192
247
|
projectPath?: string;
|
|
193
248
|
/** Geometry format to export. Defaults to `glb`. */
|
|
194
249
|
format?: GeoSpecModelFormat;
|
|
195
|
-
/** Explicit parameters passed to the
|
|
250
|
+
/** Explicit parameters passed to the exporter. */
|
|
196
251
|
parameters?: Record<string, unknown>;
|
|
197
|
-
/** Runtime client or lazy runtime factory. */
|
|
198
|
-
runtime?: GeoSpecRuntimeClient | GeoSpecRuntimeClientFactory;
|
|
199
|
-
/** Source-specific runtime adapters, e.g. host-provided GPL-isolated kernels. */
|
|
200
|
-
sourceAdapters?: readonly GeoSpecRuntimeSourceAdapter[];
|
|
201
252
|
/** STEP reader strategy used for STEP exports. */
|
|
202
253
|
stepStreaming?: StepStreamingMode;
|
|
203
254
|
/** Whether STEP loading should also produce mesh evidence. Defaults to true. */
|
|
@@ -213,12 +264,11 @@ type LoadModelFileOptions = {
|
|
|
213
264
|
*/
|
|
214
265
|
workUnitLimit?: number;
|
|
215
266
|
/**
|
|
216
|
-
* Axis-aligned millimetre box the
|
|
217
|
-
* can be checked region by region. Only
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* it never stands for the whole model.
|
|
267
|
+
* Axis-aligned millimetre box the exporter crops the model to before export, so a large mesh model
|
|
268
|
+
* can be checked region by region. Only an exporter that declares `region` honours it; any other
|
|
269
|
+
* exporter, or a crop the exporter cannot make, refuses with a typed diagnostic instead of loading
|
|
270
|
+
* the whole model. The cropped subject is a different subject: its load evidence records `region`,
|
|
271
|
+
* so a claim on it never stands for the whole model.
|
|
222
272
|
*/
|
|
223
273
|
region?: GeoSpecLoadRegion;
|
|
224
274
|
};
|
|
@@ -235,7 +285,7 @@ type LoadModelOptions<Code extends Record<string, string> = Record<string, strin
|
|
|
235
285
|
* @public
|
|
236
286
|
*/
|
|
237
287
|
type GeoSpecModelLoader = <Code extends Record<string, string> = Record<string, string>>(options: LoadModelOptions<Code>) => Promise<GeoSpecSubject>;
|
|
238
|
-
/** A configured loader whose
|
|
288
|
+
/** A configured loader whose admissions can be released with its owner. @public */
|
|
239
289
|
type ManagedGeoSpecModelLoader = GeoSpecModelLoader & {
|
|
240
290
|
dispose(): Promise<void>;
|
|
241
291
|
};
|
|
@@ -251,11 +301,9 @@ type CreateModelLoaderOptions = {
|
|
|
251
301
|
readSource?: GeoSpecNativeSourceReader;
|
|
252
302
|
/** Geometry format to export when an individual call does not specify one. */
|
|
253
303
|
format?: GeoSpecModelFormat;
|
|
254
|
-
/**
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
sourceAdapters?: readonly GeoSpecRuntimeSourceAdapter[];
|
|
258
|
-
/** Project root used by runtime integrations. */
|
|
304
|
+
/** Model exporters for source-kind files; the first whose `extensions` claim the file exports it. */
|
|
305
|
+
exporters?: readonly GeoSpecModelExporter[];
|
|
306
|
+
/** Absolute project root passed to the exporter. */
|
|
259
307
|
projectPath?: string;
|
|
260
308
|
/** STEP reader strategy used for STEP sources or exports. */
|
|
261
309
|
stepStreaming?: StepStreamingMode;
|
|
@@ -267,4 +315,4 @@ type CreateModelLoaderOptions = {
|
|
|
267
315
|
meshAngularToleranceDegrees?: number;
|
|
268
316
|
};
|
|
269
317
|
//#endregion
|
|
270
|
-
export { CreateModelLoaderOptions, GeoSpecLoadRegion,
|
|
318
|
+
export { CreateModelLoaderOptions, GeoSpecConsumedSources, GeoSpecExportFormat, GeoSpecExporterCapabilities, GeoSpecLoadRegion, GeoSpecModelArtifact, GeoSpecModelExportRequest, GeoSpecModelExporter, GeoSpecModelFormat, GeoSpecModelLoader, LoadModelCodeOptions, LoadModelFileOptions, LoadModelOptions, LoadModelSourceOptions, ManagedGeoSpecModelLoader, MeshBufferSource, MeshSource };
|
|
@@ -22,8 +22,6 @@ type GeoSpecCollector = {
|
|
|
22
22
|
};
|
|
23
23
|
/** Per-module collector configuration; native subject/engine lifetime stays with the host. @public */
|
|
24
24
|
type GeoSpecCollectorOptions = {
|
|
25
|
-
matcherWallBackstop?: number;
|
|
26
|
-
forensic?: boolean;
|
|
27
25
|
nativeAssertions: GeoSpecAssertionClientOptions;
|
|
28
26
|
/** Observes each selected test as it starts and settles, for host progress reporting. */
|
|
29
27
|
onTestEvent?: (event: GeoSpecTestEvent) => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createNodeVmFileSystem as e,resolveUnderRoot as t}from"./node-vm-filesystem.mjs";export{e as createNodeVmFileSystem,t as resolveUnderRoot};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { VmFileSystem } from "@taucad/esbuild/vm";
|
|
2
|
+
//#region src/runner/node/node-vm-filesystem.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolve one VM path to a real path under the root.
|
|
5
|
+
*
|
|
6
|
+
* @param root - The project root.
|
|
7
|
+
* @param path - A canonical path rooted by the supplied project capability.
|
|
8
|
+
* @returns The absolute host path.
|
|
9
|
+
* @throws Error when the path escapes the root.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
declare const resolveUnderRoot: (root: string, path: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Create a Node `VmFileSystem` rooted at `root`.
|
|
15
|
+
*
|
|
16
|
+
* @param root - Absolute project root path.
|
|
17
|
+
* @returns A VM filesystem confined to `root`.
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
declare const createNodeVmFileSystem: (root: string) => VmFileSystem;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { createNodeVmFileSystem, resolveUnderRoot };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{lstat as e,mkdir as t,open as n,readFile as r,realpath as i,rename as a,unlink as o}from"node:fs/promises";import{basename as s,dirname as c,isAbsolute as l,join as u,relative as d,resolve as f,sep as p}from"node:path";import{assertRootedPath as m}from"@taucad/runtime/kernel";import{randomUUID as h}from"node:crypto";const g=(e,t)=>{let n=f(e),r=f(n,m(t)),i=d(n,r);if(i.startsWith(`..${p}`)||i===`..`)throw Error(`GeoSpec refused a path outside the project root: '${t}'.`);return r},_=(e,t)=>{let n=d(e,t);return n===``||!l(n)&&n!==`..`&&!n.startsWith(`..${p}`)},v=async t=>{let n=t;for(;;)try{return await e(n),n}catch(e){let{code:t}=e;if(t!==`ENOENT`&&t!==`ENOTDIR`)throw e;let r=c(n);if(r===n)throw e;n=r}},y=l=>{let d=f(l),p=i(d),m=async(e,t)=>{let[n,r]=await Promise.all([p,v(e)]);if(!_(n,await i(r)))throw Object.assign(Error(`GeoSpec refused a symbolic link outside the project root: '${t}'.`),{code:`EACCES`});return e},y=async e=>m(g(d,e),e),b=async e=>{let t=await r(await y(e));return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)};return{exists:async e=>{try{let t=await n(await y(e),`r`);try{return(await t.stat()).isFile()||await t.readFile(),!0}finally{await t.close()}}catch(e){let{code:t}=e;if(t===`ENOENT`||t===`ENOTDIR`)return!1;throw e}},readFile:(async(e,t)=>t===`utf8`?r(await y(e),`utf8`):b(e)),writeFile:async(r,l)=>{let f=g(d,r),v=c(f);await m(v,r),await t(v,{recursive:!0});let[y,b]=await Promise.all([p,i(v)]);if(!_(y,b))throw Object.assign(Error(`GeoSpec refused a symbolic link outside the project root: '${r}'.`),{code:`EACCES`});let x=u(b,s(f));if((await e(x).catch(e=>{if(e.code!==`ENOENT`)throw e}))?.isSymbolicLink())throw Object.assign(Error(`GeoSpec refused to replace a symbolic link: '${r}'.`),{code:`ELOOP`});let S=u(b,`.${s(f)}.${process.pid}.${h()}.tmp`);try{let t=await n(S,`wx`);try{await t.writeFile(l,`utf8`),await t.sync()}finally{await t.close()}let o=await i(v);if(o!==b||!_(y,o))throw Object.assign(Error(`GeoSpec refused a changed parent directory: '${r}'.`),{code:`ELOOP`});if((await e(x).catch(e=>{if(e.code!==`ENOENT`)throw e}))?.isSymbolicLink())throw Object.assign(Error(`GeoSpec refused to replace a symbolic link: '${r}'.`),{code:`ELOOP`});await a(S,x)}finally{await o(S).catch(()=>void 0)}},ensureDir:async e=>{let n=await y(e);await t(n,{recursive:!0});let[r,a]=await Promise.all([p,i(n)]);if(!_(r,a))throw Object.assign(Error(`GeoSpec refused a symbolic link outside the project root: '${e}'.`),{code:`EACCES`})}}};export{y as createNodeVmFileSystem,g as resolveUnderRoot};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=()=>{let e=new Set;return{on(t,n){let r=e=>{e.type===t&&n(e)};return e.add(r),()=>e.delete(r)},emit(t){for(let n of e)n(t)},clear(){e.clear()}}};export{e as createRunnerEventChannel};
|
|
@@ -21,16 +21,12 @@ type GeoSpecPoolHostMessage = {
|
|
|
21
21
|
shard: GeoSpecPoolShard;
|
|
22
22
|
testNamePattern?: string;
|
|
23
23
|
testTimeout?: number;
|
|
24
|
-
matcherWallBackstop?: number;
|
|
25
|
-
forensic?: boolean;
|
|
26
24
|
} | {
|
|
27
25
|
/** List-only collection pass: register tests, run no bodies (R3 splitting). */
|
|
28
26
|
type: 'list-tests';
|
|
29
27
|
shardId: number;
|
|
30
28
|
file: string;
|
|
31
29
|
testTimeout?: number;
|
|
32
|
-
matcherWallBackstop?: number;
|
|
33
|
-
forensic?: boolean;
|
|
34
30
|
} | {
|
|
35
31
|
type: 'shutdown';
|
|
36
32
|
};
|
|
@@ -47,12 +43,6 @@ type GeoSpecPoolWorkerMessage = {
|
|
|
47
43
|
type: 'file-start';
|
|
48
44
|
shardId: number;
|
|
49
45
|
file: string;
|
|
50
|
-
} | {
|
|
51
|
-
type: 'forensic';
|
|
52
|
-
shardId: number;
|
|
53
|
-
name: string;
|
|
54
|
-
value: number;
|
|
55
|
-
unit: 'milliseconds' | 'count';
|
|
56
46
|
} | {
|
|
57
47
|
type: 'tests-listed';
|
|
58
48
|
shardId: number;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "../types.mjs";
|
|
2
|
+
import { GeoSpecPoolWorkerHandle } from "./pool-messages.mjs";
|
|
3
|
+
import { GeoSpecRunner } from "../worker/runner-types.mjs";
|
|
4
|
+
import { ShardTimings } from "./shard-timings.mjs";
|
|
5
|
+
//#region src/runner/pool/pool.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Everything the pool needs that differs between Node and the browser.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
type GeoSpecPoolOptions = {
|
|
12
|
+
/** Spawn one worker running `startGeoSpecPoolWorkerHost`. */
|
|
13
|
+
createWorker: () => GeoSpecPoolWorkerHandle | Promise<GeoSpecPoolWorkerHandle>;
|
|
14
|
+
/** Optional one-time worker initialization performed after its ready signal. */
|
|
15
|
+
initializeWorker?: (worker: GeoSpecPoolWorkerHandle) => Promise<void> | void;
|
|
16
|
+
/** Wait for each native worker's cleanup acknowledgement before terminating it. */
|
|
17
|
+
gracefulShutdown?: boolean;
|
|
18
|
+
/** Worker count. Omit to let the caller's host auto-size before calling. */
|
|
19
|
+
workers: number;
|
|
20
|
+
/** Optional scheduling telemetry. Absent = declared order, no memory class. */
|
|
21
|
+
timings?: ShardTimings;
|
|
22
|
+
/** Per-shard non-verdict watchdog, milliseconds (R11). */
|
|
23
|
+
shardTimeout?: number;
|
|
24
|
+
/** Split files whose recorded duration exceeds this. Milliseconds. */
|
|
25
|
+
splitThreshold?: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Create the host-agnostic pool runner.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Worker factory, worker count, telemetry and event hook.
|
|
31
|
+
* @returns The runner lifecycle surface.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
declare const createGeoSpecPoolRunner: (options: GeoSpecPoolOptions) => GeoSpecRunner;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { GeoSpecPoolOptions, createGeoSpecPoolRunner };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{geoSpecTimeoutCode as e}from"../timeout-codes.mjs";import{createNoMatchingGeoSpecTestsIssue as t}from"../worker/no-matching-tests-issue.mjs";import{accumulateFileResult as n}from"../worker/serial-runner.mjs";import{createRunnerEventChannel as r}from"./events.mjs";import{filesToSplit as i,planShards as a,selectShard as o}from"./shard-planner.mjs";import{assertRootedPath as s}from"@taucad/runtime/kernel";const c=(t,n)=>({code:e.shard,message:`GeoSpec shard '${t}' exceeded the ${n} ms non-verdict watchdog and its worker was terminated: infrastructure failure, not a geometry verdict.`,severity:`error`,type:`runtime`}),l=(e,t=`GEOSPEC_SHARD_FAILED`)=>({success:!1,issues:[{code:t,message:e,severity:`error`,type:`runtime`}]}),u=e=>({requestedFiles:[...e],completedFiles:[],notRunFiles:[...e],discoveryComplete:!1,discovered:0,selected:0,completed:0,passed:0,failed:0,unsupported:0,inconclusive:0,skipped:0,notRun:0,cancelled:!1,bailed:!1}),d=e=>{let t=`complete`,n=new Map;for(let r of e){r.lineage?.status===`mixed`?t=`mixed`:r.lineage?.status!==`complete`&&t!==`mixed`&&(t=`unavailable`);let e=r.lineage?.loads.flatMap(({evidence:e})=>{let t=e?.artifacts[0];return e?.exportOptions===void 0?[...(e?.artifacts??[]).flatMap(e=>e.sourcePath===void 0?[]:[{[e.sourcePath]:`sha256:${e.sha256}`}]),...e?.sourcePath===void 0||t===void 0?[]:[{[e.sourcePath]:`sha256:${t.sha256}`}]]:[]})??[];for(let i of[...r.lineage?.modules.map(e=>e.files)??[],...e,...r.lineage?.loads.map(e=>e.evidence?.sourceRevision?.files??{})??[]])for(let[e,r]of Object.entries(i))n.has(e)&&n.get(e)!==r&&(t=`mixed`),n.set(e,r)}return t},f=e=>{let t,n,r,i=new Promise(e=>{r=e}),a,o,s=new Promise((e,t)=>{a=e,o=t});s.catch(()=>void 0);let c=!1,l=!1,u,d,f=new Promise((e,t)=>{u=e,d=t});f.catch(()=>void 0);let p=e=>{let n=t;t=void 0,n?.(e)};return e.onMessage(e=>{switch(e.type){case`ready`:r?.();break;case`initialized`:c?(c=!1,a?.()):l?u?.():a?.();break;case`initialization-error`:n=e.message,r?.(),o?.(Error(e.message)),l&&d?.(Error(e.message));break;case`shard-complete`:p({type:`complete`,message:e});break;case`tests-listed`:p({type:`listed`,names:e.names});break;case`shard-error`:case`list-error`:p({type:`error`,message:e.message})}}),e.onExit(e=>{n=e.message??`the pool worker exited unexpectedly`,r?.(),o?.(Error(n)),d?.(Error(n)),p({type:`error`,message:n})}),{ready:i,initialized:s,shutdown:f,beginInitialization:()=>{c=!0},beginShutdown:()=>{l=!0},exited:()=>n,settle:async e=>n===void 0?new Promise(e=>{t=e}):{type:`error`,message:n}}},p=p=>{let h={closed:!1},g=()=>h.closed,_=()=>h.aborted,v=[],y=new WeakMap,b=r(),x=!1,S,C,w=async e=>{if(S!==void 0)return S;let t=(async()=>{for(;v.length<e&&!g();){let e=await p.createWorker();if(g()){await e.terminate();return}y.set(e,f(e)),v.push({handle:e,busy:!1,dead:!1,initialized:!1})}})();S=t;try{await t}finally{S===t&&(S=void 0)}},T=async(e,t,n)=>{let r=y.get(e.handle).settle(n.id);if(t(),p.shardTimeout===void 0)return r;let i=p.shardTimeout,a,o=new Promise(e=>{a=setTimeout(()=>{e({type:`timeout`,message:c(n.file,i).message})},i)});try{let t=await Promise.race([r,o]);return t.type===`timeout`&&(e.dead=!0,await e.handle.terminate()),t}finally{clearTimeout(a)}},E=()=>C===void 0?(h.closed=!0,h.aborted=`closed`,C=(async()=>{try{await S}catch{}let e=v.filter(e=>!e.dead&&y.get(e.handle)?.exited()===void 0);try{for(let t of e)y.get(t.handle).beginShutdown(),t.handle.postMessage({type:`shutdown`});p.gracefulShutdown&&await Promise.all(e.map(async e=>{let t=y.get(e.handle),n;try{await Promise.race([t.shutdown,new Promise((e,t)=>{n=setTimeout(()=>{t(Error(`Native pool shutdown timed out.`))},1e4)})])}finally{clearTimeout(n)}}))}finally{await Promise.all(v.map(async e=>e.handle.terminate())),v.length=0,b.emit({type:`close`}),b.clear()}})(),C):C;return{async run(r){let c=u(r.files);if(new Set(r.files).size!==r.files.length)return{success:!1,passed:0,failed:1,selectedTests:0,files:[],accounting:c,lineageStatus:`unavailable`,issues:[{code:`GEOSPEC_DUPLICATE_FILES`,message:`GeoSpec requested files must be unique.`,severity:`error`,type:`runtime`}]};if(h.closed)return{success:!1,passed:0,failed:1,selectedTests:0,files:[],accounting:c,lineageStatus:`unavailable`,issues:[{code:`GEOSPEC_RUNNER_CLOSED`,message:`GeoSpec runner is closed.`,severity:`error`,type:`runtime`}]};if(x)return{success:!1,passed:0,failed:1,selectedTests:0,files:[],accounting:c,lineageStatus:`unavailable`,issues:[{code:`GEOSPEC_RUNNER_BUSY`,message:`GeoSpec runner already has an active run.`,severity:`error`,type:`runtime`}]};x=!0;try{delete h.aborted;let c=r.files.map(s),f=performance.now();b.emit({type:`run-start`,files:c});try{if(await w(Math.max(1,p.workers)),g())throw Error(`GeoSpec pool closed during worker creation.`);if(await Promise.all(v.map(async e=>y.get(e.handle).ready)),g())throw Error(`GeoSpec pool closed during worker initialization.`);for(let e of v){let t=y.get(e.handle).exited();if(t!==void 0)throw Error(t)}if(p.initializeWorker&&await Promise.all(v.map(async e=>{if(e.initialized)return;let t=y.get(e.handle);t.beginInitialization(),await p.initializeWorker(e.handle),await t.initialized,e.initialized=!0})),g())throw Error(`GeoSpec pool closed during worker initialization.`)}catch(e){throw await E(),e}let x={passed:0,failed:0,selectedTests:0},S=[],C=[],D=new Map,O=c.length<v.length?[...new Set([...c,...i(c,p.timings,p.splitThreshold)])]:i(c,p.timings,p.splitThreshold);for(let[e,t]of O.entries()){let n=v[e%v.length],i={id:-1-e,file:t},a=await T(n,()=>{n.handle.postMessage({type:`list-tests`,shardId:i.id,file:t,...r.testTimeout===void 0?{}:{testTimeout:r.testTimeout}})},i);a.type===`listed`&&a.names.length>1&&new Set(a.names).size===a.names.length&&D.set(t,a.names)}let k=a({files:c,splitTests:D,...p.timings?{timings:p.timings}:{}}),A=new Map,j=new Map(c.map(e=>[e,k.filter(t=>t.file===e).length])),M=new Map,N=!1,P=0,F=async t=>{for(;!t.dead;){if(_()!==void 0)return;let n=o({pending:k,workerLoadKey:t.loadKey,heavyRunning:P});if(n===void 0)return;let[i]=k.splice(n,1);i.memoryClass===`heavy`&&(P+=1),t.busy=!0,b.emit({type:`file-start`,file:i.file});let a=performance.now(),s=await T(t,()=>{t.handle.postMessage({type:`run-shard`,shard:{id:i.id,file:i.file,...i.testNamePattern===void 0?{}:{testNamePattern:i.testNamePattern}},...r.testNamePattern===void 0?{}:{testNamePattern:String(r.testNamePattern)},...r.testTimeout===void 0?{}:{testTimeout:r.testTimeout}})},i),c=performance.now()-a;t.busy=!1,i.memoryClass===`heavy`&&--P;let u=s.type===`complete`?s.message.result:s.type===`timeout`?l(s.message,e.shard):l(s.type===`listed`?`the pool worker answered a shard with a test list`:s.message);s.type===`complete`&&(t.loadKey=s.message.primaryLoadKey??t.loadKey,p.timings?.record(i.timingKey,{durationMs:s.message.durationMs,peakRssBytes:s.message.workerMemoryBytes??0})),s.type===`timeout`&&C.push({code:e.shard,message:s.message,severity:`error`,type:`runtime`});let d=A.get(i.file);A.set(i.file,{result:d===void 0?u:m(d.result,u),durationMs:(d?.durationMs??0)+c}),M.set(i.file,(M.get(i.file)??0)+1),b.emit({type:`file-complete`,file:i.file,result:u,durationMs:c,...s.type===`complete`&&s.message.primaryLoadKey!==void 0?{primaryLoadKey:s.message.primaryLoadKey}:{},...s.type===`complete`&&s.message.workerMemoryBytes!==void 0?{workerMemoryBytes:s.message.workerMemoryBytes}:{}});let f=!u.success||!u.passed;if(r.bail===!0&&f){N=!0,k.length=0;return}}};await Promise.all(v.map(async e=>F(e))),p.timings?.save();let I=_();I!==void 0&&(C.push({code:`GEOSPEC_RUNNER_ABORTED`,message:I.length>0?`GeoSpec run aborted: ${I}`:`GeoSpec run aborted.`,severity:`error`,type:`runtime`}),x.failed+=1,b.emit({type:`abort`,reason:I}));for(let e of c){let t=A.get(e);t!==void 0&&(S.push({file:e,result:t.result,durationMs:t.durationMs}),n(x,t.result))}x.selectedTests===0&&x.failed===0&&(C.push(t()),x.failed+=1);let L=u(r.files);L.completedFiles=c.filter(e=>M.get(e)===j.get(e)),L.notRunFiles=c.filter(e=>!L.completedFiles.includes(e)),L.cancelled=I!==void 0,L.bailed=N,L.discoveryComplete=L.notRunFiles.length===0&&S.every(({result:e})=>e.success&&e.accounting!==void 0);for(let{result:e}of S)if(e.accounting!==void 0)for(let t of[`discovered`,`selected`,`completed`,`passed`,`failed`,`unsupported`,`inconclusive`,`skipped`,`notRun`])L[t]+=e.accounting[t];let R=d(S.map(({result:e})=>e)),z={success:L.discoveryComplete&&S.every(({result:e})=>e.success&&e.passed)&&R===`complete`&&x.failed===0&&C.length===0,passed:x.passed,failed:x.failed,selectedTests:x.selectedTests,files:S,accounting:L,lineageStatus:R,...C.length>0?{issues:C}:{},durationMs:performance.now()-f};return b.emit({type:`run-complete`,result:z}),z}finally{x=!1}},on:b.on,abort(e){h.aborted=e??`requested`},close:E}},m=(e,t)=>{let n=[...e.tests??[],...t.tests??[]].sort((e,t)=>(e.ordinal??1/0)-(t.ordinal??1/0)),r={status:d([e,t]),modules:[...e.lineage?.modules??[],...t.lineage?.modules??[]],loads:[...e.lineage?.loads??[],...t.lineage?.loads??[]]},i,a=n.map(e=>e.ordinal),o=e.accounting?.discovered;if(o!==void 0&&o===t.accounting?.discovered&&a.every(e=>e!==void 0&&Number.isSafeInteger(e)&&e>=0&&e<o)&&new Set(a).size===n.length){let e=e=>n.filter(t=>t.status===e).length;i={discovered:o,selected:n.length,completed:n.filter(e=>e.status!==`not-run`&&e.status!==`skipped`).length,passed:e(`passed`),failed:e(`failed`),unsupported:e(`unsupported`),inconclusive:e(`inconclusive`),skipped:e(`skipped`),notRun:Math.max(0,o-n.length)+e(`not-run`)}}let s={tests:n,lineage:r,...i===void 0?{}:{accounting:i}};return!e.success||!t.success?{success:!1,issues:[...e.success?[]:e.issues,...t.success?[]:t.issues],...e.bundle===void 0&&t.bundle===void 0?{}:{bundle:e.bundle??t.bundle},...s}:{success:!0,passed:e.passed&&t.passed&&i!==void 0&&r.status===`complete`,...s,bundle:e.bundle}};export{p as createGeoSpecPoolRunner,m as mergeShardResults};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>t===void 0?e:`${e}::${t}`,t=6e4,n=3758096384,r=e=>`^${e.replaceAll(/[$()*+.?[\\\]^{|}]/gu,String.raw`\$&`)}$`,i=e=>e!==void 0&&e.peakRssBytes>=3758096384?`heavy`:`normal`,a=(e,n,r=t)=>n?e.filter(e=>(n.read(e)?.durationMs??0)>r):[],o=t=>{let{files:n,splitTests:a,timings:o,affinity:s}=t,c=[];for(let[t,l]of n.entries()){let n=a?.get(l),u=s?.get(l);if(n&&n.length>0){for(let a of n){let n=r(a),s=e(l,n),d=o?.read(s);c.push({file:l,testNamePattern:n,timingKey:s,estimated:d?.durationMs??0,memoryClass:i(d),...u===void 0?{}:{affinityKey:u},order:t})}continue}let d=o?.read(l);c.push({file:l,timingKey:l,estimated:d?.durationMs??0,memoryClass:i(d),...u===void 0?{}:{affinityKey:u},order:t})}return c.sort((e,t)=>t.estimated-e.estimated||e.order-t.order),c.map(({order:e,...t},n)=>({...t,id:n}))},s=e=>{let{pending:t,workerLoadKey:n,heavyRunning:r}=e,i=e=>e.memoryClass===`normal`||r===0;if(n!==void 0){let e=t.findIndex(e=>e.affinityKey===n&&i(e));if(e!==-1)return e}let a=t.findIndex(e=>i(e));return a===-1?void 0:a};export{n as defaultHeavyRssBytes,t as defaultSplitThreshold,r as exactTestPattern,a as filesToSplit,o as planShards,s as selectShard,e as shardTimingKey};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/runner/pool/shard-timings.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shard timings vocabulary: the pool's longest-first scheduling hint, kept
|
|
4
|
+
* free of Node imports so the browser-safe pool can type it. The persisted
|
|
5
|
+
* table lives in `#runner/node/pool-timings.js`.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* One shard's recorded cost.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
type ShardTiming = {
|
|
15
|
+
/** Wall duration of the last recorded run, in milliseconds. */
|
|
16
|
+
durationMs: number;
|
|
17
|
+
/** Highest RSS observed for this shard, in bytes. Monotonic. */
|
|
18
|
+
peakRssBytes: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A mutable timings table.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
type ShardTimings = {
|
|
26
|
+
/** Merge one observation. Peak RSS only ever grows. */
|
|
27
|
+
record(shard: string, timing: ShardTiming): void;
|
|
28
|
+
/** The hint for one shard, or `undefined` when unknown. */
|
|
29
|
+
read(shard: string): ShardTiming | undefined;
|
|
30
|
+
/** Every recorded shard, sorted by descending duration (longest-first). */
|
|
31
|
+
longestFirst(): string[];
|
|
32
|
+
/** Persist the table. Failure is silent: telemetry never fails a run. */
|
|
33
|
+
save(): void;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ShardTiming, ShardTimings };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=e=>{let t=e;if(t?.kind!==`geometry-subject`)return e;let n=t.provenance?.contentHash,r=t.provenance?.source?.format;return{kind:`geometry-subject-ref`,...typeof n==`string`?{contentHash:n}:{},...typeof r==`string`?{format:r}:{}}},t=t=>({...t,subject:e(t.subject)}),n=e=>({...e,assertions:e.assertions.map(e=>t(e))}),r=e=>{let t=e=>({...e,code:``,...e.sourceMap===void 0?{}:{sourceMap:``}});return e.success?{...e,tests:e.tests.map(e=>n(e)),bundle:t(e.bundle)}:{...e,...e.tests===void 0?{}:{tests:e.tests.map(e=>n(e))},...e.bundle===void 0?{}:{bundle:t(e.bundle)}}};export{r as sanitizePoolResult};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ManagedGeoSpecNativeModelLoader } from "../../model/native-model-loader.mjs";
|
|
2
|
+
import { GeoSpecPoolHostMessage, GeoSpecPoolWorkerMessage } from "./pool-messages.mjs";
|
|
3
|
+
import { GeoSpecRunnerOptions } from "../worker/runner-types.mjs";
|
|
4
|
+
//#region src/runner/pool/worker-host.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Options accepted by {@link startGeoSpecPoolWorkerHost}.
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
type GeoSpecPoolWorkerHostOptions = {
|
|
11
|
+
/** Filesystem containing the project and test modules. */
|
|
12
|
+
filesystem: GeoSpecRunnerOptions['filesystem'];
|
|
13
|
+
/** Native assertion client shared with this worker's model admissions. */
|
|
14
|
+
nativeAssertions: GeoSpecRunnerOptions['nativeAssertions'];
|
|
15
|
+
/** Managed native admissions, released after every shard and collection pass and before shutdown. */
|
|
16
|
+
nativeModelLoader: ManagedGeoSpecNativeModelLoader;
|
|
17
|
+
/** Additional in-memory modules made available to the VM. */
|
|
18
|
+
builtinModules?: GeoSpecRunnerOptions['builtinModules'];
|
|
19
|
+
/** Post a message to the pool host. */
|
|
20
|
+
postMessage: (message: GeoSpecPoolWorkerMessage) => void;
|
|
21
|
+
/** Subscribe to pool-host messages. */
|
|
22
|
+
onHostMessage: (listener: (message: GeoSpecPoolHostMessage) => void) => void;
|
|
23
|
+
/** Sample this worker's resident memory in bytes (R15 telemetry); optional. */
|
|
24
|
+
measureMemoryBytes?: () => number | undefined;
|
|
25
|
+
/** Release platform resources on shutdown, after the native subjects are released. */
|
|
26
|
+
onShutdown?: () => Promise<void> | void;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Serve shards until the host says shutdown.
|
|
30
|
+
*
|
|
31
|
+
* @param options - Worker filesystem, native engine, and message plumbing.
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
declare const startGeoSpecPoolWorkerHost: (options: GeoSpecPoolWorkerHostOptions) => void;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { GeoSpecPoolWorkerHostOptions, startGeoSpecPoolWorkerHost };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{executeGeoSpecFile as e}from"../worker/serial-runner.mjs";import{sanitizePoolResult as t}from"./transport.mjs";const n=e=>e instanceof Error?e.message:String(e),r=e=>e.success?e.tests.map(e=>[...e.suite,e.name].join(` > `)):[],i=i=>{let a={filesystem:i.filesystem,nativeAssertions:i.nativeAssertions,nativeModelLoader:i.nativeModelLoader,...i.builtinModules?{builtinModules:i.builtinModules}:{}},o=new Map,s=async()=>{await i.nativeModelLoader.releaseAll()},c=async(e,t)=>{let n=e;try{await s()}catch(r){e.type!==`shard-error`&&e.type!==`list-error`&&(n=t(r))}try{i.postMessage(n)}catch(e){i.postMessage(t(e))}},l=Promise.resolve(),u=e=>{let t=l;l=(async()=>{try{await t}catch{}try{await e()}catch(e){try{i.postMessage({type:`initialization-error`,message:n(e)})}catch{}}})()},d=async l=>{if(l.type===`initialize`){i.postMessage({type:`initialized`});return}if(l.type===`shutdown`){o.clear();let e=!1,t;try{await s()}catch(n){e=!0,t=n}try{await i.onShutdown?.()}catch(n){e||(e=!0,t=n)}if(e){i.postMessage({type:`initialization-error`,message:n(t)});return}i.postMessage({type:`initialized`});return}if(l.type===`list-tests`){let t=e=>({type:`list-error`,shardId:l.shardId,file:l.file,message:n(e)}),i;try{let t=await e({runner:a,file:l.file,collectOnly:!0,bundleCache:o,...l.testTimeout===void 0?{}:{testTimeout:l.testTimeout}});i={type:`tests-listed`,shardId:l.shardId,file:l.file,names:r(t)}}catch(e){i=t(e)}await c(i,t);return}let{shard:u}=l;i.postMessage({type:`file-start`,shardId:u.id,file:u.file});let d=performance.now(),f=e=>({type:`shard-error`,shardId:u.id,file:u.file,message:n(e)}),p;try{let n=await e({runner:a,file:u.file,bundleCache:o,...u.testNamePattern===void 0?l.testNamePattern===void 0?{}:{testNamePattern:l.testNamePattern}:{testNamePattern:u.testNamePattern},...l.testTimeout===void 0?{}:{testTimeout:l.testTimeout}}),r=i.measureMemoryBytes?.();p={type:`shard-complete`,shardId:u.id,file:u.file,result:t(n),durationMs:performance.now()-d,...r===void 0?{}:{workerMemoryBytes:r}}}catch(e){p=f(e)}await c(p,f)};i.onHostMessage(e=>{u(async()=>d(e))}),i.postMessage({type:`ready`})};export{i as startGeoSpecPoolWorkerHost};
|
|
@@ -44,4 +44,4 @@ export const createModelLoader = (defaults = {}) => {
|
|
|
44
44
|
}
|
|
45
45
|
return factory(defaults);
|
|
46
46
|
};
|
|
47
|
-
`;async function w(m){let h=a(m.testNamePattern);if(!h.success)return{success:!1,issues:[h.issue]};let g=v(m.filesystem),_=await l({filesystem:g.filesystem}),x=s({...m.
|
|
47
|
+
`;async function w(m){let h=a(m.testNamePattern);if(!h.success)return{success:!1,issues:[h.issue]};let g=v(m.filesystem),_=await l({filesystem:g.filesystem}),x=s({...m.onTestEvent===void 0?{}:{onTestEvent:m.onTestEvent},nativeAssertions:m.nativeAssertions}),w=await y(m),T=f(),E=b(),D=new Set,O=[],k=async()=>{do{let e=[...D];await Promise.allSettled(e);for(let t of e)D.delete(t)}while(D.size>0)},A=m.nativeModelLoader,j=r(m.nativeAssertions),M=!0,N=A===void 0?void 0:n=>{let r=O.length,a=`${T}:load:${r+1}`;O.push({loadId:a,status:`unavailable`});let o=(async()=>{try{let i=await A(n),{load:o,...s}=i,c=t(i),l=o===void 0?void 0:{...structuredClone(o),loadId:a};return O[r]={loadId:a,status:l?.status??`unavailable`,subject:s,...l===void 0?{}:{evidence:l}},e({engine:m.nativeAssertions.engine,client:j,identity:s,...l===void 0?{}:{load:l},isLive:()=>M,...c===void 0?{}:{ensureResident:c}})}catch(e){throw O[r]={loadId:a,status:`failed`,error:e instanceof Error?e.message:String(e),...e instanceof i?{diagnostics:e.diagnostics}:{}},e}})();return D.add(o.catch(()=>void 0)),D.add(o),o};E.set(T,{collector:x,geoSpecModelLoadError:i,...N?{nativeModelLoader:N}:{},...N?{createModelLoader:t=>{if(t.engine!==void 0||t.readSource!==void 0)throw TypeError(`Authored VM model defaults cannot replace the admitting host.`);let r=0,i=new Set,a=new Set,o,s=async i=>{let o=r,s=await N({...t,...i}),c=n(s);return c.lease!==void 0&&a.add(c.lease),e({...c,isLive:()=>c.isLive()&&r===o})},c=e=>{let t=s(e);return i.add(t),t},l=async()=>{do{let e=[...i];await Promise.allSettled(e);for(let t of e)i.delete(t)}while(i.size>0);r+=1;let e=[];for(let t of a)try{t.dispose(),a.delete(t)}catch(t){e.push(t)}if(e.length>0)throw AggregateError(e,`GeoSpec model scope disposal failed.`)};return Object.assign(c,{dispose(){return o??=l().finally(()=>{o=void 0}),o}})}}:{}}),_.registerModule(`geospec`,{version:`0.0.0-poc`,code:S(T)}),_.registerModule(`geospec/model`,{version:`0.0.0-poc`,code:C(T)});for(let[e,t]of Object.entries(m.builtinModules??{}))_.registerModule(e,t);try{let e=w?.bundle??await _.bundle(m.entryPath);if(!e.success)return{success:!1,issues:e.issues,bundle:e};let t=g.reads();m.bundleCache!==void 0&&w===void 0&&t!==void 0&&m.bundleCache.set(m.entryPath,{builtinIdentity:p(m),runToken:T,bundle:e,bundlerReads:t});let n=await _.execute(w===void 0?e.code:e.code.replaceAll(w.runToken,T)),r=async()=>{let t={},n=g.reads()!==void 0;for(let e of w?.bundlerReads??g.observed()){if(e.question===`exists`||typeof e.answer==`boolean`)continue;let r=await c(typeof e.answer==`string`?new TextEncoder().encode(e.answer):e.answer);n&&=t[e.path]===void 0||t[e.path]===`sha256:${r}`,t[e.path]=`sha256:${r}`}let r=new Map(Object.entries(t));for(let e of O){let{evidence:t}=e;for(let e of t?.exportOptions===void 0?t?.artifacts??[]:[])if(e.sourcePath!==void 0){let t=`sha256:${e.sha256}`;n&&=!r.has(e.sourcePath)||r.get(e.sourcePath)===t,r.set(e.sourcePath,t)}if(t?.sourcePath!==void 0&&t.exportOptions===void 0){let e=t.artifacts[0];if(e!==void 0){let i=`sha256:${e.sha256}`;n&&=!r.has(t.sourcePath)||r.get(t.sourcePath)===i,r.set(t.sourcePath,i)}}for(let[t,i]of Object.entries(e.evidence?.sourceRevision?.files??{}))n&&=!r.has(t)||r.get(t)===i,r.set(t,i)}return{status:n?O.some(e=>e.status!==`complete`)?`unavailable`:`complete`:`mixed`,modules:[{entryPath:m.entryPath,bundleSha256:await c(new TextEncoder().encode(w===void 0?e.code:e.code.replaceAll(w.runToken,T))),files:t,consistent:n}],loads:O}},i=e=>({discovered:x.tests.length,selected:e,completed:x.tests.filter(e=>e.status!==`not-run`&&e.status!==`skipped`).length,passed:x.tests.filter(e=>e.status===`passed`).length,failed:x.tests.filter(e=>e.status===`failed`).length,unsupported:x.tests.filter(e=>e.status===`unsupported`).length,inconclusive:x.tests.filter(e=>e.status===`inconclusive`).length,skipped:x.tests.filter(e=>e.status===`skipped`).length,notRun:x.tests.filter(e=>e.status===`not-run`).length});if(!n.success)return await k(),{success:!1,issues:n.issues,bundle:e,tests:x.tests,accounting:i(0),lineage:await r()};if(m.collectOnly===!0)return await x.waitForCompletion(m.testTimeout,/(?!)/u),await k(),{success:!0,passed:!1,tests:x.tests,bundle:e,accounting:i(0),lineage:await r()};await x.waitForCompletion(m.testTimeout,h.pattern),await k();let a=o(x.tests,h.pattern),s=await r();return{success:!0,passed:s.status===`complete`&&a.every(e=>e.status===`passed`||e.status===`skipped`),tests:a,bundle:e,accounting:i(a.length),lineage:s}}finally{await k(),M=!1,E.delete(T),E.size===0&&Reflect.deleteProperty(d,u),_.dispose()}}export{w as runGeoSpecModule};
|
package/dist/runner/types.d.mts
CHANGED
|
@@ -710,10 +710,6 @@ type RunGeoSpecModuleOptions = {
|
|
|
710
710
|
testNamePattern?: string | RegExp;
|
|
711
711
|
/** Timeout for async test callbacks, in milliseconds. */
|
|
712
712
|
testTimeout?: number;
|
|
713
|
-
/** Milliseconds. Non-verdict matcher infrastructure backstop. */
|
|
714
|
-
matcherWallBackstop?: number;
|
|
715
|
-
/** Emit structured forensic events for this run. */
|
|
716
|
-
forensic?: boolean;
|
|
717
713
|
/** Observes each selected test as it starts and settles, for host progress reporting. */
|
|
718
714
|
onTestEvent?: (event: GeoSpecTestEvent) => void;
|
|
719
715
|
/**
|
|
@@ -2,5 +2,8 @@ import { DiscoverGeoSpecFilesOptions, GeoSpecDiscoveryFileSystem, GeoSpecDiscove
|
|
|
2
2
|
import { GeoSpecTimeoutCode, geoSpecTimeoutCode, isGeoSpecTimeoutCode } from "../timeout-codes.mjs";
|
|
3
3
|
import { createNoMatchingGeoSpecTestsIssue } from "./no-matching-tests-issue.mjs";
|
|
4
4
|
import { GeoSpecPoolHostMessage, GeoSpecPoolShard, GeoSpecPoolWorkerHandle, GeoSpecPoolWorkerMessage } from "../pool/pool-messages.mjs";
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import { GeoSpecRunner, GeoSpecRunnerEvent, GeoSpecRunnerFileResult, GeoSpecRunnerOptions, GeoSpecRunnerResult, GeoSpecRunnerRunOptions } from "./runner-types.mjs";
|
|
6
|
+
import { ShardTiming, ShardTimings } from "../pool/shard-timings.mjs";
|
|
7
|
+
import { GeoSpecPoolOptions, createGeoSpecPoolRunner } from "../pool/pool.mjs";
|
|
8
|
+
import { GeoSpecPoolWorkerHostOptions, startGeoSpecPoolWorkerHost } from "../pool/worker-host.mjs";
|
|
9
|
+
export { type DiscoverGeoSpecFilesOptions, type GeoSpecDiscoveryFileSystem, type GeoSpecDiscoveryResult, type GeoSpecPoolHostMessage, type GeoSpecPoolOptions, type GeoSpecPoolShard, type GeoSpecPoolWorkerHandle, type GeoSpecPoolWorkerHostOptions, type GeoSpecPoolWorkerMessage, type GeoSpecRunner, type GeoSpecRunnerEvent, type GeoSpecRunnerFileResult, type GeoSpecRunnerOptions, type GeoSpecRunnerResult, type GeoSpecRunnerRunOptions, type GeoSpecTimeoutCode, type ShardTiming, type ShardTimings, createGeoSpecPoolRunner, createNoMatchingGeoSpecTestsIssue, discoverGeoSpecFiles, geoSpecTimeoutCode, isGeoSpecTimeoutCode, startGeoSpecPoolWorkerHost };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{geoSpecTimeoutCode as e,isGeoSpecTimeoutCode as t}from"../timeout-codes.mjs";import{discoverGeoSpecFiles as n}from"../discovery.mjs";import{createNoMatchingGeoSpecTestsIssue as r}from"./no-matching-tests-issue.mjs";export{r as createNoMatchingGeoSpecTestsIssue,n as discoverGeoSpecFiles,e as geoSpecTimeoutCode,t as isGeoSpecTimeoutCode};
|
|
1
|
+
import{geoSpecTimeoutCode as e,isGeoSpecTimeoutCode as t}from"../timeout-codes.mjs";import{discoverGeoSpecFiles as n}from"../discovery.mjs";import{createNoMatchingGeoSpecTestsIssue as r}from"./no-matching-tests-issue.mjs";import{createGeoSpecPoolRunner as i}from"../pool/pool.mjs";import{startGeoSpecPoolWorkerHost as a}from"../pool/worker-host.mjs";export{i as createGeoSpecPoolRunner,r as createNoMatchingGeoSpecTestsIssue,n as discoverGeoSpecFiles,e as geoSpecTimeoutCode,t as isGeoSpecTimeoutCode,a as startGeoSpecPoolWorkerHost};
|
|
@@ -89,20 +89,12 @@ type GeoSpecRunnerEvent = {
|
|
|
89
89
|
} | {
|
|
90
90
|
type: 'run-complete';
|
|
91
91
|
result: GeoSpecRunnerResult;
|
|
92
|
-
} |
|
|
92
|
+
} | {
|
|
93
93
|
type: 'abort';
|
|
94
94
|
reason?: string;
|
|
95
95
|
} | {
|
|
96
96
|
type: 'close';
|
|
97
97
|
};
|
|
98
|
-
/** One structured forensic measurement emitted by a runner. @public */
|
|
99
|
-
type GeoSpecForensicEvent = {
|
|
100
|
-
type: 'forensic';
|
|
101
|
-
name: string;
|
|
102
|
-
value: number;
|
|
103
|
-
unit: 'milliseconds' | 'count';
|
|
104
|
-
shardId?: number;
|
|
105
|
-
};
|
|
106
98
|
/**
|
|
107
99
|
* Options accepted by a GeoSpec worker-style runner run.
|
|
108
100
|
*
|
|
@@ -115,16 +107,12 @@ type GeoSpecRunnerRunOptions = {
|
|
|
115
107
|
testNamePattern?: string | RegExp;
|
|
116
108
|
/** Timeout for each async test callback, in milliseconds. */
|
|
117
109
|
testTimeout?: number;
|
|
118
|
-
/** Non-verdict matcher wall backstop. Milliseconds. */
|
|
119
|
-
matcherWallBackstop?: number;
|
|
120
110
|
/**
|
|
121
111
|
* Engine work-unit budget for this run's claims, replacing the runner's
|
|
122
112
|
* `nativeAssertions.workUnitLimit`. A `loadModel` or `expectGeo` budget still
|
|
123
113
|
* wins for its own claims.
|
|
124
114
|
*/
|
|
125
115
|
workUnitLimit?: number;
|
|
126
|
-
/** Emit structured forensic measurements for this run. */
|
|
127
|
-
forensic?: boolean;
|
|
128
116
|
/**
|
|
129
117
|
* Stop after the first failing file (R1). Interactive fail-fast only —
|
|
130
118
|
* never the default for reward runs, which want the complete red set.
|
|
@@ -164,4 +152,4 @@ type GeoSpecRunner = {
|
|
|
164
152
|
close(): Promise<void>;
|
|
165
153
|
};
|
|
166
154
|
//#endregion
|
|
167
|
-
export {
|
|
155
|
+
export { GeoSpecRunner, GeoSpecRunnerAccounting, GeoSpecRunnerEvent, GeoSpecRunnerFileResult, GeoSpecRunnerOptions, GeoSpecRunnerResult, GeoSpecRunnerRunOptions, GeoSpecRunnerTestRef };
|