@bldrs-ai/conway 1.449.1351 → 1.451.1357

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/README.md CHANGED
@@ -185,14 +185,25 @@ git merge -X rename-threshold=25 --no-commit
185
185
 
186
186
  # CI and Testing
187
187
 
188
+ CI is **tiered** so full-corpus regression + performance cost is paid once per
189
+ release candidate, not on every push. Full architecture, cost rationale, and
190
+ the rc/re-bless/LFS runbook: [design/new/ci-regression-cost.md](design/new/ci-regression-cost.md).
191
+
188
192
  Every PR is gated on two checks defined in `.github/workflows/build.yml`:
189
193
 
190
194
  | Job | What it does |
191
195
  |---|---|
192
196
  | `build` | `yarn install`, WASM + TS compile (WASM cached on the `conway-geom` submodule SHA), `yarn test`, `yarn lint`, and a Tier-A geometry-digest check of the in-repo `data/` models against committed goldens. |
193
- | `run-ifc-regression` | `needs: build`. Reuses the same WASM cache. Runs the regression batch against the public `test-models` ref (`TEST_MODELS_REF`, default `main`), pinned per-run to the resolved commit SHA; posts a per-PR comment with the resolved SHA + `failed.csv` / `errors.csv` / perf summaries, and uploads the candidate npm tarball + `perf.csv` as workflow artifacts. |
197
+ | `run-ifc-regression` | `needs: build`. Reuses the same WASM cache. Runs the regression batch over the **smoke subset** (`regression/smoke_models.txt`) of the public `test-models` ref (`TEST_MODELS_REF`, default `main`), pinned per-run to the resolved commit SHA. Fails on any `failed.csv` row; digest *changes* are informational (reviewed via the visual-diff comment, blessed at the rc). Posts a per-PR comment with the resolved SHA + smoke-scoped `failed.csv` / `errors.csv` / perf summaries, and uploads the candidate npm tarball + `perf.csv` as workflow artifacts. |
198
+
199
+ A `concurrency` group cancels superseded PR runs (main runs are never
200
+ cancelled, so releases always complete). A merge to `main` re-runs those two
201
+ jobs and then chains into `auto-publish` (see [Releases](#releases) below).
194
202
 
195
- A merge to `main` re-runs those two jobs and then chains into `auto-publish` (see [Releases](#releases) below).
203
+ **The full public+private corpus runs once per release candidate** push an
204
+ `rc-*` tag and `rc-regression.yml` regenerates every baseline (opening a
205
+ reviewable baseline PR per test-models repo) while the `perf-three-*` jobs run
206
+ the full benchmark. See the runbook in the CI-cost doc linked above.
196
207
 
197
208
  ## Regression batch
198
209
 
@@ -209,8 +220,11 @@ The `build` job also runs a fast, hermetic geometry gate over every `data/*.ifc`
209
220
  **Tier 1 — Conway-only perf in CI (live).** Every regression run emits a `perf.csv` of `parseTimeMs / geometryTimeMs / totalTimeMs / rssMb / heapUsedMb / heapTotalMb` per model. The top-10 slowest are posted in the PR comment; the full CSV is uploaded as a workflow artifact. This piggybacks on the existing regression batch so cost is ~0 extra runner minutes.
210
221
 
211
222
  **Tier 2 — full headless-three perf in CI (live).** Two jobs,
212
- `perf-three-public` and `perf-three-private`, run on `push: main`
213
- (`needs: run-ifc-regression`). Each downloads the candidate Conway tarball
223
+ `perf-three-public` and `perf-three-private`, run on `rc-*` tags and
224
+ `workflow_dispatch` (`needs: run-ifc-regression`) once per release
225
+ candidate, not per merge, so per-model timings stay low-variance on the
226
+ isolated runner (see [ci-regression-cost.md](design/new/ci-regression-cost.md)
227
+ for why frequency, not runner size, is the cost lever). Each downloads the candidate Conway tarball
214
228
  that the regression job packed, clones [headless-three](https://github.com/bldrs-ai/headless-three)
215
229
  at a pinned `H3_SHA`, and forces the whole H3 → adapter → conway chain onto
216
230
  the candidate via a yarn `resolutions` override (no `yarn link`), then runs
@@ -305,23 +319,23 @@ npm dist-tag add @bldrs-ai/conway@<VERSION> stable
305
319
 
306
320
  # Roadmap
307
321
 
308
- The CI / release pipeline is continuous and complete: `build` gates
309
- `run-ifc-regression`, which gates the headless-three perf jobs, and every
310
- green merge to `main` auto-publishes (see [Releases](#releases)). The
322
+ The CI / release pipeline is continuous and **tiered**: `build` (fixtures) and
323
+ `run-ifc-regression` (smoke subset) gate every PR; the full public+private
324
+ corpus and the headless-three perf jobs run once per `rc-*` release candidate;
325
+ every green merge to `main` auto-publishes (see [Releases](#releases)). The
326
+ architecture, cost rationale, and rc/re-bless/LFS runbook are in
327
+ [design/new/ci-regression-cost.md](design/new/ci-regression-cost.md). Regression
328
+ renders (the visual-diff comment) and per-model perf-in-CI shipped; the
311
329
  umbrella waterfall (#316) and performance-in-CI (#314) issues are closed.
312
- The optional follow-ups below remain.
313
-
314
- ### Headless-three perf on PRs
315
-
316
- The `perf-three-public` / `perf-three-private` jobs run on `push: main`
317
- only — to keep PR wall-time down and to keep the private-models token off
318
- PR events (forks can't be trusted with it). Running them per-PR, behind a
319
- gate that withholds the private job from fork PRs, would catch H3
320
- render-time regressions before merge instead of just after.
321
-
322
- ### Golden `errors.csv` diffing + regression renders (#288)
323
330
 
324
- Fail a PR when its `errors.csv` diverges from a checked-in golden — "New
325
- errors detected. Copy errors.csv to golden/errors.csv to accept changes"
326
- and attach per-model renders to the regression comment so geometry
327
- regressions are visible without downloading artifacts.
331
+ ### Open follow-ups
332
+
333
+ - **Errors as a hard gate.** `errors.csv` is regenerated and reviewed (and,
334
+ at the rc, blessed into the baseline), but a PR is not *failed* on unexpected
335
+ new errors — only on parse/extract failures (`failed.csv`). A golden-errors
336
+ gate could fail smoke-scoped error churn that isn't an intended change.
337
+ - **Smoke-list curation.** The smoke subset (`regression/smoke_models.txt`)
338
+ is a hand-picked spread; as the engine's failure surface shifts, revisit
339
+ which models best catch regressions cheaply.
340
+ - **Perf-threshold gating.** The `perf-three-*` jobs post deltas but don't
341
+ fail an rc on a regression; a threshold could turn perf into a release gate.
@@ -30,7 +30,7 @@ var import_node_process = require("node:process");
30
30
  var readline = __toESM(require("node:readline"), 1);
31
31
 
32
32
  // compiled/src/version/version.js
33
- var versionString = "Conway v1.449.1351";
33
+ var versionString = "Conway v1.451.1357";
34
34
 
35
35
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
36
36
  var wasmType = "";
@@ -14965,7 +14965,7 @@ ${t5.join("\n")}` : "";
14965
14965
  var import_process = require("process");
14966
14966
 
14967
14967
  // compiled/src/version/version.js
14968
- var versionString = "Conway v1.449.1351";
14968
+ var versionString = "Conway v1.451.1357";
14969
14969
 
14970
14970
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
14971
14971
  function pThreadsAllowed() {
@@ -15943,7 +15943,7 @@ var ParsingBuffer = class {
15943
15943
  };
15944
15944
 
15945
15945
  // compiled/src/version/version.js
15946
- var versionString = "Conway v1.449.1351";
15946
+ var versionString = "Conway v1.451.1357";
15947
15947
 
15948
15948
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
15949
15949
  function pThreadsAllowed() {
@@ -944,7 +944,7 @@ var EntityTypesIfcCount = 909;
944
944
  var entity_types_ifc_gen_default = EntityTypesIfc;
945
945
 
946
946
  // compiled/src/version/version.js
947
- var versionString = "Conway v1.449.1351";
947
+ var versionString = "Conway v1.451.1357";
948
948
 
949
949
  // compiled/dependencies/conway-geom/interface/conway_geometry.js
950
950
  var wasmType = "";
@@ -242,6 +242,40 @@ async function runForFile(filePath, outputPath, maxTimeout, perfPath) {
242
242
  hash: fileHash,
243
243
  };
244
244
  }
245
+ /**
246
+ * Run a file's digest, retrying ONCE if the first attempt times out.
247
+ *
248
+ * A per-model timeout is not a deterministic parse/geometry failure — it
249
+ * surfaces as a failed.csv row with an empty code AND empty signal (the
250
+ * TimeoutError path sets neither) and, historically, has come from transient
251
+ * core oversubscription on the CI runner rather than the model itself.
252
+ * ISSUE_159_kleine_Wohnung_R22.ifc (a geometry-dense ~18.5k-item model) has
253
+ * reddened the rc-regression gate this way twice: first co-scheduled at
254
+ * --concurrency 2, then again running ALONE at --concurrency 1, so serializing
255
+ * the batch was not enough to make it reliable.
256
+ *
257
+ * Digest regeneration is idempotent, so a second attempt is safe: a transient
258
+ * timeout clears on the retry, while a genuine hang times out both times and
259
+ * still lands in failed.csv — the gate keeps its protective value against a
260
+ * model that truly never loads. Only timeouts are retried; a real non-zero
261
+ * exit or signal is returned immediately (those ARE deterministic).
262
+ *
263
+ * @param filePath Model file to digest.
264
+ * @param outputPath Digest output path (without extension).
265
+ * @param maxTimeout Per-attempt timeout in ms.
266
+ * @param perfPath Optional path the child writes its one-row perf CSV to.
267
+ * @return The first attempt's result, or the retry's result on a timeout.
268
+ */
269
+ async function runForFileWithTimeoutRetry(filePath, outputPath, maxTimeout, perfPath) {
270
+ const timedOut = (r) => r.type === 'Failed' && r.message === 'Execution timed out';
271
+ const first = await runForFile(filePath, outputPath, maxTimeout, perfPath);
272
+ if (!timedOut(first)) {
273
+ return first;
274
+ }
275
+ console.log(`"${path.basename(filePath)}" timed out; retrying once ` +
276
+ `(a transient timeout clears, a true hang times out again).`);
277
+ return runForFile(filePath, outputPath, maxTimeout, perfPath);
278
+ }
245
279
  // Model files the regression harness understands: IFC plus STEP AP214.
246
280
  const SUPPORTED_MODEL_EXTENSIONS = ['.ifc', '.stp', '.step'];
247
281
  /**
@@ -344,7 +378,7 @@ async function processIFCFilesInParallel(ifcFiles, outputPath, errorLines, fileL
344
378
  const perfChildPath = perfDir ?
345
379
  path.join(perfDir, `${path.parse(ifcPath).name}.perf.csv`) :
346
380
  undefined;
347
- const fileResults = await runForFile(ifcPath, path.join(outputPath, path.parse(ifcPath).name), maxTimeout, perfChildPath);
381
+ const fileResults = await runForFileWithTimeoutRetry(ifcPath, path.join(outputPath, path.parse(ifcPath).name), maxTimeout, perfChildPath);
348
382
  activeTasks--;
349
383
  console.log(`Completed task for "${path.basename(ifcPath)}". Active tasks: ${activeTasks}`);
350
384
  return { ifcPath, fileResults };
@@ -396,7 +430,7 @@ async function recursiveWalk(parentPath, excludeRegex, outputPath, errorLines, f
396
430
  const perfChildPath = perfDir ?
397
431
  path.join(perfDir, `${path.parse(resolved).name}.perf.csv`) :
398
432
  undefined;
399
- const fileResults = await runForFile(resolved, path.join(outputPath, path.parse(resolved).name), maxTimeout, perfChildPath);
433
+ const fileResults = await runForFileWithTimeoutRetry(resolved, path.join(outputPath, path.parse(resolved).name), maxTimeout, perfChildPath);
400
434
  if (fileResults.type === 'Run') {
401
435
  if (fileResults.errorLines !== void 0) {
402
436
  errorLines.push(...fileResults.errorLines);
@@ -5,5 +5,5 @@
5
5
  // only the first segment (major) is meaningful and is the one CI carries forward.
6
6
  // Must stay in `vN.N.N` shape: the CI stamp regex, scripts/updateVersion.mjs, and
7
7
  // statistics.ts all match `v\d+\.\d+\.\d+`.
8
- const versionString = 'Conway v1.449.1351';
8
+ const versionString = 'Conway v1.451.1357';
9
9
  export { versionString };