@lark-apaas/fullstack-cli 1.1.59-alpha.20260720153322 → 1.1.59-alpha.20260720184331

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.59-alpha.20260720153322",
3
+ "version": "1.1.59-alpha.20260720184331",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,9 +9,11 @@ const DEFAULT_MAX_RUN_BYTES = 2 * 1024 * 1024;
9
9
  const DEFAULT_MAX_ROOT_BYTES = 64 * 1024 * 1024;
10
10
  const DEFAULT_MAX_ROOT_DIRECTORIES = 128;
11
11
  const DEFAULT_MAX_ROOT_SCAN_DIRECTORIES = 256;
12
+ const DEFAULT_MAX_RUN_SCAN_ENTRIES = 64;
12
13
  const RUN_ID_PATTERN = /^prv_[A-Za-z0-9_-]{1,60}$/;
13
14
  const STATE_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
14
15
  const PROCESS_START_TICKS_PATTERN = /^\d+$/;
16
+ const PRODUCER_EPOCH_PATTERN = /^ppe_[a-f0-9]{32}$/;
15
17
 
16
18
  function readLinuxProcessStartTicks(fileSystem, pid) {
17
19
  try {
@@ -51,6 +53,7 @@ function createPreviewPhaseOutbox(options) {
51
53
  const randomUUID = options.random_uuid || require('crypto').randomUUID;
52
54
  const now = options.now || Date.now;
53
55
  const writerInstanceID = options.writer_instance_id || randomUUID();
56
+ const producerEpoch = options.producer_epoch;
54
57
  const getProcessStartTicks =
55
58
  options.get_process_start_ticks ||
56
59
  (pid => readLinuxProcessStartTicks(fileSystem, pid));
@@ -65,16 +68,18 @@ function createPreviewPhaseOutbox(options) {
65
68
  options.max_root_directories ?? DEFAULT_MAX_ROOT_DIRECTORIES;
66
69
  const maxRootScanDirectories =
67
70
  options.max_root_scan_directories ?? DEFAULT_MAX_ROOT_SCAN_DIRECTORIES;
71
+ const maxRunScanEntries =
72
+ options.max_run_scan_entries ?? DEFAULT_MAX_RUN_SCAN_ENTRIES;
68
73
  if (!RUN_ID_PATTERN.test(options.run_id || '')) {
69
74
  throw new Error('invalid preview run_id for phase outbox');
70
75
  }
76
+ if (!PRODUCER_EPOCH_PATTERN.test(producerEpoch || '')) {
77
+ throw new Error('invalid preview phase producer_epoch');
78
+ }
71
79
  if (!STATE_ID_PATTERN.test(writerInstanceID)) {
72
80
  throw new Error('invalid preview phase writer_instance_id');
73
81
  }
74
- if (
75
- currentProcessStartTicks != null &&
76
- !PROCESS_START_TICKS_PATTERN.test(currentProcessStartTicks)
77
- ) {
82
+ if (!PROCESS_START_TICKS_PATTERN.test(currentProcessStartTicks || '')) {
78
83
  throw new Error('invalid preview phase process_start_ticks');
79
84
  }
80
85
  if (!Number.isSafeInteger(maxRunBytes) || maxRunBytes <= 0) {
@@ -92,6 +97,9 @@ function createPreviewPhaseOutbox(options) {
92
97
  ) {
93
98
  throw new Error('invalid preview phase max_root_scan_directories');
94
99
  }
100
+ if (!Number.isSafeInteger(maxRunScanEntries) || maxRunScanEntries <= 0) {
101
+ throw new Error('invalid preview phase max_run_scan_entries');
102
+ }
95
103
  const runDir = path.join(root, options.run_id);
96
104
  const eventsPath = path.join(runDir, 'events.jsonl');
97
105
  const metaPath = path.join(runDir, 'meta.json');
@@ -99,6 +107,10 @@ function createPreviewPhaseOutbox(options) {
99
107
  const sealPath = path.join(runDir, 'sealed.json');
100
108
  const degradedPath = path.join(runDir, 'degraded.json');
101
109
  const quiesceRequestPath = path.join(runDir, 'quiesce.request.json');
110
+ const producerEpochMarkerPath = path.join(
111
+ root,
112
+ `producer-epoch-${producerEpoch}.json`
113
+ );
102
114
  let recovered = false;
103
115
  let metadata = null;
104
116
  let writerLockOwned = false;
@@ -109,6 +121,10 @@ function createPreviewPhaseOutbox(options) {
109
121
  let sealRequested = false;
110
122
  let closed = false;
111
123
  let closeResult = false;
124
+ let producerMarkerActive = false;
125
+ let producerMarkerTerminal = false;
126
+ let producerStartedAtMs = null;
127
+ let producerTerminalAtMs = null;
112
128
 
113
129
  function rootQuotaError(message) {
114
130
  const error = new Error(message);
@@ -116,13 +132,30 @@ function createPreviewPhaseOutbox(options) {
116
132
  return error;
117
133
  }
118
134
 
119
- function inspectRootUsage() {
120
- const entries = fileSystem.readdirSync(root, { withFileTypes: true });
121
- if (entries.length > maxRootScanDirectories) {
122
- throw rootQuotaError(
123
- `preview phase root scan exceeds ${maxRootScanDirectories} entries`
124
- );
135
+ function readDirectoryEntriesBounded(directoryPath, limit, quotaMessage) {
136
+ const entries = [];
137
+ const directory = fileSystem.opendirSync(directoryPath);
138
+ try {
139
+ while (entries.length <= limit) {
140
+ const entry = directory.readSync();
141
+ if (!entry) break;
142
+ entries.push(entry);
143
+ if (entries.length > limit) {
144
+ throw rootQuotaError(quotaMessage);
145
+ }
146
+ }
147
+ } finally {
148
+ directory.closeSync();
125
149
  }
150
+ return entries;
151
+ }
152
+
153
+ function inspectRootUsage() {
154
+ const entries = readDirectoryEntriesBounded(
155
+ root,
156
+ maxRootScanDirectories,
157
+ `preview phase root scan exceeds ${maxRootScanDirectories} entries`
158
+ );
126
159
  let directories = 0;
127
160
  let bytes = 0;
128
161
  const disappearingEntry = error =>
@@ -152,9 +185,11 @@ function createPreviewPhaseOutbox(options) {
152
185
  directories += 1;
153
186
  let runEntries;
154
187
  try {
155
- runEntries = fileSystem.readdirSync(entryPath, {
156
- withFileTypes: true,
157
- });
188
+ runEntries = readDirectoryEntriesBounded(
189
+ entryPath,
190
+ maxRunScanEntries,
191
+ `preview phase run scan exceeds ${maxRunScanEntries} entries: ${entryPath}`
192
+ );
158
193
  } catch (error) {
159
194
  if (disappearingEntry(error)) continue;
160
195
  throw error;
@@ -223,6 +258,19 @@ function createPreviewPhaseOutbox(options) {
223
258
  }
224
259
  }
225
260
 
261
+ function syncRootDirectory() {
262
+ const flags =
263
+ fs.constants.O_RDONLY |
264
+ (fs.constants.O_DIRECTORY || 0) |
265
+ (fs.constants.O_NOFOLLOW || 0);
266
+ const fd = fileSystem.openSync(root, flags);
267
+ try {
268
+ fileSystem.fsyncSync(fd);
269
+ } finally {
270
+ fileSystem.closeSync(fd);
271
+ }
272
+ }
273
+
226
274
  function writeAll(fd, payload) {
227
275
  let offset = 0;
228
276
  while (offset < payload.length) {
@@ -239,6 +287,24 @@ function createPreviewPhaseOutbox(options) {
239
287
  }
240
288
  }
241
289
 
290
+ function writeDurableTempFile(tempPath, payload) {
291
+ let fd = null;
292
+ try {
293
+ const flags =
294
+ fs.constants.O_CREAT |
295
+ fs.constants.O_EXCL |
296
+ fs.constants.O_WRONLY |
297
+ (fs.constants.O_NOFOLLOW || 0);
298
+ fd = fileSystem.openSync(tempPath, flags, 0o600);
299
+ writeAll(fd, payload);
300
+ fileSystem.fsyncSync(fd);
301
+ } finally {
302
+ if (fd != null) {
303
+ fileSystem.closeSync(fd);
304
+ }
305
+ }
306
+ }
307
+
242
308
  function publishImmutableState(filePath, label, value) {
243
309
  const tempPath = path.join(
244
310
  runDir,
@@ -272,6 +338,194 @@ function createPreviewPhaseOutbox(options) {
272
338
  }
273
339
  }
274
340
 
341
+ function producerMarkerConflict() {
342
+ const error = new Error(
343
+ `preview phase producer epoch already belongs to another writer: ${producerEpoch}`
344
+ );
345
+ error.code = 'PREVIEW_PHASE_PRODUCER_EPOCH_CONFLICT';
346
+ return error;
347
+ }
348
+
349
+ function validateProducerMarker(marker) {
350
+ const terminal = marker?.state === 'terminal';
351
+ if (
352
+ marker?.protocol !== 'preview-phase-producer-epoch-v1' ||
353
+ marker.schema_version !== 1 ||
354
+ marker.epoch !== producerEpoch ||
355
+ !RUN_ID_PATTERN.test(marker.run_id || '') ||
356
+ !STATE_ID_PATTERN.test(marker.generation || '') ||
357
+ !STATE_ID_PATTERN.test(marker.writer_instance_id || '') ||
358
+ !Number.isSafeInteger(marker.pid) ||
359
+ marker.pid <= 0 ||
360
+ !PROCESS_START_TICKS_PATTERN.test(marker.process_start_ticks || '') ||
361
+ !Number.isFinite(marker.started_at_ms) ||
362
+ marker.started_at_ms <= 0 ||
363
+ (marker.state !== 'active' && !terminal) ||
364
+ (terminal &&
365
+ (!Number.isFinite(marker.terminal_at_ms) ||
366
+ marker.terminal_at_ms < marker.started_at_ms)) ||
367
+ (!terminal && marker.terminal_at_ms !== undefined)
368
+ ) {
369
+ throw new Error(
370
+ `invalid preview phase producer epoch marker: ${producerEpochMarkerPath}`
371
+ );
372
+ }
373
+ return marker;
374
+ }
375
+
376
+ function markerBelongsToThisWriter(marker) {
377
+ return (
378
+ marker.epoch === producerEpoch &&
379
+ marker.run_id === options.run_id &&
380
+ marker.generation === metadata.generation &&
381
+ marker.writer_instance_id === writerInstanceID &&
382
+ marker.pid === process.pid &&
383
+ (marker.process_start_ticks || '') === (currentProcessStartTicks || '')
384
+ );
385
+ }
386
+
387
+ function readAndValidateProducerMarker() {
388
+ return validateProducerMarker(
389
+ readRegularJSON(producerEpochMarkerPath, 'producer epoch marker')
390
+ );
391
+ }
392
+
393
+ function buildProducerMarker(state) {
394
+ return {
395
+ protocol: 'preview-phase-producer-epoch-v1',
396
+ schema_version: 1,
397
+ epoch: producerEpoch,
398
+ run_id: options.run_id,
399
+ generation: metadata.generation,
400
+ writer_instance_id: writerInstanceID,
401
+ pid: process.pid,
402
+ process_start_ticks: currentProcessStartTicks,
403
+ started_at_ms: producerStartedAtMs,
404
+ state,
405
+ ...(state === 'terminal' ? { terminal_at_ms: producerTerminalAtMs } : {}),
406
+ };
407
+ }
408
+
409
+ function publishActiveProducerMarker() {
410
+ if (producerMarkerActive) return;
411
+ if (!metadata || !writerLockOwned) {
412
+ throw new Error(
413
+ 'preview phase producer marker requires writer ownership'
414
+ );
415
+ }
416
+ if (fileSystem.existsSync(producerEpochMarkerPath)) {
417
+ const existing = readAndValidateProducerMarker();
418
+ if (!markerBelongsToThisWriter(existing) || existing.state !== 'active') {
419
+ throw producerMarkerConflict();
420
+ }
421
+ producerStartedAtMs = existing.started_at_ms;
422
+ producerMarkerActive = true;
423
+ return;
424
+ }
425
+
426
+ producerStartedAtMs = producerStartedAtMs ?? now();
427
+ const marker = buildProducerMarker('active');
428
+ const tempPath = path.join(
429
+ root,
430
+ `.producer-epoch.${producerEpoch}.${process.pid}.${writerInstanceID}.tmp`
431
+ );
432
+ let fd = null;
433
+ try {
434
+ const flags =
435
+ fs.constants.O_CREAT |
436
+ fs.constants.O_EXCL |
437
+ fs.constants.O_WRONLY |
438
+ (fs.constants.O_NOFOLLOW || 0);
439
+ fd = fileSystem.openSync(tempPath, flags, 0o600);
440
+ writeAll(fd, Buffer.from(`${JSON.stringify(marker)}\n`, 'utf8'));
441
+ fileSystem.fsyncSync(fd);
442
+ fileSystem.closeSync(fd);
443
+ fd = null;
444
+ try {
445
+ fileSystem.linkSync(tempPath, producerEpochMarkerPath);
446
+ syncRootDirectory();
447
+ } catch (error) {
448
+ if (error?.code !== 'EEXIST') throw error;
449
+ const existing = readAndValidateProducerMarker();
450
+ if (
451
+ !markerBelongsToThisWriter(existing) ||
452
+ existing.state !== 'active'
453
+ ) {
454
+ throw producerMarkerConflict();
455
+ }
456
+ producerStartedAtMs = existing.started_at_ms;
457
+ }
458
+ producerMarkerActive = true;
459
+ } finally {
460
+ if (fd != null) {
461
+ try {
462
+ fileSystem.closeSync(fd);
463
+ } catch {}
464
+ }
465
+ let tempRemoved = false;
466
+ try {
467
+ fileSystem.rmSync(tempPath, { force: true });
468
+ tempRemoved = true;
469
+ } catch {
470
+ // A stale temp file is never accepted as lifecycle proof.
471
+ }
472
+ if (tempRemoved) syncRootDirectory();
473
+ }
474
+ }
475
+
476
+ function publishTerminalProducerMarker() {
477
+ if (producerMarkerTerminal) return;
478
+ const existing = readAndValidateProducerMarker();
479
+ if (!markerBelongsToThisWriter(existing)) {
480
+ throw producerMarkerConflict();
481
+ }
482
+ producerStartedAtMs = existing.started_at_ms;
483
+ if (existing.state === 'terminal') {
484
+ producerTerminalAtMs = existing.terminal_at_ms;
485
+ producerMarkerActive = true;
486
+ producerMarkerTerminal = true;
487
+ return;
488
+ }
489
+ producerTerminalAtMs =
490
+ producerTerminalAtMs ?? Math.max(now(), producerStartedAtMs);
491
+ const marker = buildProducerMarker('terminal');
492
+ const tempPath = path.join(
493
+ root,
494
+ `.producer-terminal.${producerEpoch}.${process.pid}.${writerInstanceID}.tmp`
495
+ );
496
+ let fd = null;
497
+ try {
498
+ const flags =
499
+ fs.constants.O_CREAT |
500
+ fs.constants.O_EXCL |
501
+ fs.constants.O_WRONLY |
502
+ (fs.constants.O_NOFOLLOW || 0);
503
+ fd = fileSystem.openSync(tempPath, flags, 0o600);
504
+ writeAll(fd, Buffer.from(`${JSON.stringify(marker)}\n`, 'utf8'));
505
+ fileSystem.fsyncSync(fd);
506
+ fileSystem.closeSync(fd);
507
+ fd = null;
508
+ fileSystem.renameSync(tempPath, producerEpochMarkerPath);
509
+ syncRootDirectory();
510
+ producerMarkerActive = true;
511
+ producerMarkerTerminal = true;
512
+ } finally {
513
+ if (fd != null) {
514
+ try {
515
+ fileSystem.closeSync(fd);
516
+ } catch {}
517
+ }
518
+ let tempRemoved = false;
519
+ try {
520
+ fileSystem.rmSync(tempPath, { force: true });
521
+ tempRemoved = true;
522
+ } catch {
523
+ // A stale temp file is never accepted as lifecycle proof.
524
+ }
525
+ if (tempRemoved) syncRootDirectory();
526
+ }
527
+ }
528
+
275
529
  function readAndValidateMetadata() {
276
530
  const stat = fileSystem.lstatSync(metaPath);
277
531
  if (!stat.isFile() || stat.isSymbolicLink()) {
@@ -282,6 +536,7 @@ function createPreviewPhaseOutbox(options) {
282
536
  if (
283
537
  metadata.protocol !== 'preview-phase-outbox-v1' ||
284
538
  metadata.schema_version !== 1 ||
539
+ metadata.producer_epoch !== producerEpoch ||
285
540
  metadata.run_id !== options.run_id ||
286
541
  !/^[A-Za-z0-9_-]{1,128}$/.test(metadata.generation || '') ||
287
542
  !Number.isFinite(metadata.created_at_ms) ||
@@ -303,6 +558,7 @@ function createPreviewPhaseOutbox(options) {
303
558
  const candidate = {
304
559
  protocol: 'preview-phase-outbox-v1',
305
560
  schema_version: 1,
561
+ producer_epoch: producerEpoch,
306
562
  generation: randomUUID(),
307
563
  run_id: options.run_id,
308
564
  created_at_ms: now(),
@@ -313,13 +569,13 @@ function createPreviewPhaseOutbox(options) {
313
569
  `.meta.${process.pid}.${candidate.generation}.tmp`
314
570
  );
315
571
  try {
316
- fileSystem.writeFileSync(tempPath, `${JSON.stringify(candidate)}\n`, {
317
- encoding: 'utf8',
318
- flag: 'wx',
319
- mode: 0o600,
320
- });
572
+ writeDurableTempFile(
573
+ tempPath,
574
+ Buffer.from(`${JSON.stringify(candidate)}\n`, 'utf8')
575
+ );
321
576
  try {
322
577
  fileSystem.linkSync(tempPath, metaPath);
578
+ syncRunDirectory();
323
579
  } catch (error) {
324
580
  if (error?.code !== 'EEXIST') throw error;
325
581
  }
@@ -386,11 +642,10 @@ function createPreviewPhaseOutbox(options) {
386
642
  `.writer.${process.pid}.${writerInstanceID}.tmp`
387
643
  );
388
644
  try {
389
- fileSystem.writeFileSync(tempPath, `${JSON.stringify(lock)}\n`, {
390
- encoding: 'utf8',
391
- flag: 'wx',
392
- mode: 0o600,
393
- });
645
+ writeDurableTempFile(
646
+ tempPath,
647
+ Buffer.from(`${JSON.stringify(lock)}\n`, 'utf8')
648
+ );
394
649
  try {
395
650
  fileSystem.linkSync(tempPath, writerLockPath);
396
651
  writerLockOwned = true;
@@ -586,6 +841,7 @@ function createPreviewPhaseOutbox(options) {
586
841
  ensureMetadata();
587
842
  assertGenerationIsOpen();
588
843
  acquireWriterLock();
844
+ publishActiveProducerMarker();
589
845
  // Any existing writer identity is authoritative to JS. Dead-owner
590
846
  // recovery and torn-tail finalization belong exclusively to shell-server.
591
847
  assertGenerationIsNotDegraded();
@@ -699,10 +955,25 @@ function createPreviewPhaseOutbox(options) {
699
955
  const seal = readAndValidateSeal();
700
956
  fallbackRequired ||= seal.fallback_required === true;
701
957
  if (writerLockOwned) {
958
+ publishActiveProducerMarker();
959
+ publishTerminalProducerMarker();
702
960
  removeOwnedWriterLock();
703
961
  } else if (fileSystem.existsSync(writerLockPath)) {
704
962
  closeResult = false;
705
963
  return false;
964
+ } else {
965
+ const marker = readAndValidateProducerMarker();
966
+ if (
967
+ !markerBelongsToThisWriter(marker) ||
968
+ marker.state !== 'terminal'
969
+ ) {
970
+ closeResult = false;
971
+ return false;
972
+ }
973
+ producerStartedAtMs = marker.started_at_ms;
974
+ producerTerminalAtMs = marker.terminal_at_ms;
975
+ producerMarkerActive = true;
976
+ producerMarkerTerminal = true;
706
977
  }
707
978
  closed = true;
708
979
  closeResult = true;
@@ -712,6 +983,7 @@ function createPreviewPhaseOutbox(options) {
712
983
  if (!writerLockOwned) {
713
984
  acquireWriterLock();
714
985
  }
986
+ publishActiveProducerMarker();
715
987
  observeDegradedMarker();
716
988
  if (eventsFD != null) {
717
989
  fileSystem.fdatasyncSync(eventsFD);
@@ -747,6 +1019,7 @@ function createPreviewPhaseOutbox(options) {
747
1019
  sealed_at_ms: now(),
748
1020
  fallback_required: fallbackRequired,
749
1021
  });
1022
+ publishTerminalProducerMarker();
750
1023
  removeOwnedWriterLock();
751
1024
  closeResult = true;
752
1025
  closed = true;
@@ -777,6 +1050,7 @@ function createPreviewPhaseOutbox(options) {
777
1050
  seal_path: sealPath,
778
1051
  degraded_path: degradedPath,
779
1052
  quiesce_request_path: quiesceRequestPath,
1053
+ producer_epoch_marker_path: producerEpochMarkerPath,
780
1054
  };
781
1055
  }
782
1056
 
@@ -786,6 +1060,7 @@ module.exports = {
786
1060
  DEFAULT_MAX_ROOT_BYTES,
787
1061
  DEFAULT_MAX_ROOT_DIRECTORIES,
788
1062
  DEFAULT_MAX_ROOT_SCAN_DIRECTORIES,
1063
+ DEFAULT_MAX_RUN_SCAN_ENTRIES,
789
1064
  MAX_EVENT_BYTES,
790
1065
  createPreviewPhaseOutbox,
791
1066
  };
@@ -4,7 +4,10 @@ const fs = require('fs');
4
4
  const crypto = require('crypto');
5
5
  const net = require('net');
6
6
  const path = require('path');
7
- const { createPreviewPhaseOutbox } = require('./preview-phase-outbox.cjs');
7
+ const {
8
+ MAX_EVENT_BYTES,
9
+ createPreviewPhaseOutbox,
10
+ } = require('./preview-phase-outbox.cjs');
8
11
 
9
12
  const PREFIX = '[MiaodaPreviewPhase] ';
10
13
  const RUN_ID_PATTERN = /^prv_[A-Za-z0-9_-]{1,60}$/;
@@ -86,18 +89,28 @@ function createPreviewPhaseReporter(options = {}) {
86
89
  });
87
90
  const producerInstanceID = randomUUID();
88
91
  const runId = env.MIAODA_PREVIEW_RUN_ID || '';
89
- const outbox = RUN_ID_PATTERN.test(runId)
90
- ? createPreviewPhaseOutbox({
92
+ let outbox = null;
93
+ let outboxInitializationError = null;
94
+ if (RUN_ID_PATTERN.test(runId)) {
95
+ try {
96
+ outbox = createPreviewPhaseOutbox({
91
97
  root: options.outbox_root || env.MIAODA_PREVIEW_PHASE_OUTBOX_ROOT,
92
98
  run_id: runId,
99
+ producer_epoch: env.MIAODA_PREVIEW_PHASE_PRODUCER_EPOCH,
100
+ process_start_ticks: options.process_start_ticks,
101
+ get_process_start_ticks: options.get_process_start_ticks,
93
102
  fs: options.fs,
94
103
  random_uuid: randomUUID,
95
104
  now,
96
- })
97
- : null;
105
+ });
106
+ } catch (error) {
107
+ outboxInitializationError = error;
108
+ }
109
+ }
98
110
  const producerSequences = new Map();
99
111
  const startupTerminalPhases = new Set();
100
112
  let reporterSealed = false;
113
+ let terminalCloseScheduled = false;
101
114
  let quiescePollTimer = null;
102
115
 
103
116
  function reportDiagnostic(diagnostic) {
@@ -108,6 +121,33 @@ function createPreviewPhaseReporter(options = {}) {
108
121
  }
109
122
  }
110
123
 
124
+ function scheduleTerminalClose() {
125
+ if (terminalCloseScheduled || reporterSealed) return;
126
+ terminalCloseScheduled = true;
127
+ queueMicrotask(() => {
128
+ terminalCloseScheduled = false;
129
+ if (reporterSealed) return;
130
+ // backend_tcp_ready's synchronous onReady callback emits the initial
131
+ // deferred-typecheck decision immediately after the readiness event.
132
+ // Closing in a microtask keeps that terminal startup decision inside the
133
+ // same durable generation without extending the outbox into later source
134
+ // changes or daemon restarts.
135
+ flush();
136
+ close();
137
+ });
138
+ }
139
+
140
+ if (outboxInitializationError) {
141
+ reportDiagnostic({
142
+ code: 'outbox_initialization_failed',
143
+ run_id: runId,
144
+ error:
145
+ outboxInitializationError instanceof Error
146
+ ? outboxInitializationError.message
147
+ : String(outboxInitializationError),
148
+ });
149
+ }
150
+
111
151
  function emitForProducer(eventProducer, phase, status, detail = {}) {
112
152
  if (!RUN_ID_PATTERN.test(runId) || reporterSealed) return false;
113
153
  const atMs = detail.at_ms == null ? now() : detail.at_ms;
@@ -132,34 +172,60 @@ function createPreviewPhaseReporter(options = {}) {
132
172
  recorded_at_ms: now(),
133
173
  transport: 'outbox',
134
174
  };
175
+ let serializedEvent = JSON.stringify(event);
176
+ if (Buffer.byteLength(serializedEvent, 'utf8') > MAX_EVENT_BYTES) {
177
+ reportDiagnostic({
178
+ code: 'event_rejected',
179
+ reason: 'event_too_large',
180
+ run_id: runId,
181
+ phase,
182
+ event_id: event.event_id,
183
+ });
184
+ return false;
185
+ }
135
186
  let persisted = false;
136
- try {
137
- persisted = outbox.append(event);
138
- } catch (error) {
139
- if (error?.code === 'PREVIEW_PHASE_EVENT_TOO_LARGE') {
140
- reportDiagnostic({
141
- code: 'event_rejected',
142
- reason: 'event_too_large',
143
- run_id: runId,
144
- phase,
145
- event_id: event.event_id,
146
- });
147
- return false;
148
- }
187
+ if (!outbox) {
149
188
  event.transport = 'stdout_fallback';
150
- if (error?.code !== 'PREVIEW_PHASE_OUTBOX_DEGRADED') {
151
- reportDiagnostic({
152
- code: 'outbox_append_failed',
153
- run_id: runId,
154
- phase,
155
- event_id: event.event_id,
156
- error: error instanceof Error ? error.message : String(error),
157
- });
189
+ } else {
190
+ try {
191
+ persisted = outbox.append(event);
192
+ } catch (error) {
193
+ if (error?.code === 'PREVIEW_PHASE_EVENT_TOO_LARGE') {
194
+ reportDiagnostic({
195
+ code: 'event_rejected',
196
+ reason: 'event_too_large',
197
+ run_id: runId,
198
+ phase,
199
+ event_id: event.event_id,
200
+ });
201
+ return false;
202
+ }
203
+ event.transport = 'stdout_fallback';
204
+ if (error?.code !== 'PREVIEW_PHASE_OUTBOX_DEGRADED') {
205
+ reportDiagnostic({
206
+ code: 'outbox_append_failed',
207
+ run_id: runId,
208
+ phase,
209
+ event_id: event.event_id,
210
+ error: error instanceof Error ? error.message : String(error),
211
+ });
212
+ }
158
213
  }
159
214
  }
215
+ serializedEvent = JSON.stringify(event);
216
+ if (Buffer.byteLength(serializedEvent, 'utf8') > MAX_EVENT_BYTES) {
217
+ reportDiagnostic({
218
+ code: 'event_rejected',
219
+ reason: 'event_too_large',
220
+ run_id: runId,
221
+ phase,
222
+ event_id: event.event_id,
223
+ });
224
+ return false;
225
+ }
160
226
  let mirrored = false;
161
227
  try {
162
- write(`${PREFIX}${JSON.stringify(event)}`);
228
+ write(`${PREFIX}${serializedEvent}`);
163
229
  mirrored = true;
164
230
  } catch (error) {
165
231
  reportDiagnostic({
@@ -178,8 +244,7 @@ function createPreviewPhaseReporter(options = {}) {
178
244
  ) {
179
245
  startupTerminalPhases.add(phase);
180
246
  if (startupTerminalPhases.size === 2) {
181
- flush();
182
- close();
247
+ scheduleTerminalClose();
183
248
  }
184
249
  }
185
250
  return delivered;
@@ -247,7 +312,7 @@ function createPreviewPhaseReporter(options = {}) {
247
312
 
248
313
  function flush() {
249
314
  if (reporterSealed) return true;
250
- if (!outbox) return true;
315
+ if (!outbox) return outboxInitializationError == null;
251
316
  try {
252
317
  return outbox.flush();
253
318
  } catch (error) {
@@ -288,7 +353,7 @@ function createPreviewPhaseReporter(options = {}) {
288
353
 
289
354
  function close() {
290
355
  if (reporterSealed) return true;
291
- if (!outbox) return true;
356
+ if (!outbox) return outboxInitializationError == null;
292
357
  try {
293
358
  const sealed = outbox.close();
294
359
  if (sealed) {