@riddledc/riddle-proof 0.5.2 → 0.5.4

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.
@@ -262,7 +262,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
262
262
  blocking?: boolean;
263
263
  details?: Record<string, unknown>;
264
264
  ok: boolean;
265
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
265
+ action: "ship" | "setup" | "recon" | "author" | "implement" | "verify" | "run";
266
266
  state_path: string;
267
267
  stage: any;
268
268
  summary: string;
@@ -342,7 +342,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
342
342
  continueWithStage?: WorkflowStage | null;
343
343
  blocking?: boolean;
344
344
  details?: Record<string, unknown>;
345
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
345
+ action: "ship" | "setup" | "recon" | "author" | "implement" | "verify" | "run";
346
346
  state_path: string;
347
347
  stage: any;
348
348
  checkpoint: string;
@@ -589,7 +589,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
589
589
  error?: undefined;
590
590
  } | {
591
591
  ok: boolean;
592
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
592
+ action: "ship" | "setup" | "recon" | "author" | "implement" | "verify" | "run";
593
593
  state_path: string;
594
594
  stage: any;
595
595
  summary: string;
@@ -262,7 +262,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
262
262
  blocking?: boolean;
263
263
  details?: Record<string, unknown>;
264
264
  ok: boolean;
265
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
265
+ action: "ship" | "setup" | "recon" | "author" | "implement" | "verify" | "run";
266
266
  state_path: string;
267
267
  stage: any;
268
268
  summary: string;
@@ -342,7 +342,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
342
342
  continueWithStage?: WorkflowStage | null;
343
343
  blocking?: boolean;
344
344
  details?: Record<string, unknown>;
345
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
345
+ action: "ship" | "setup" | "recon" | "author" | "implement" | "verify" | "run";
346
346
  state_path: string;
347
347
  stage: any;
348
348
  checkpoint: string;
@@ -589,7 +589,7 @@ declare function executeWorkflow(params: WorkflowParams, pluginConfig: any, reso
589
589
  error?: undefined;
590
590
  } | {
591
591
  ok: boolean;
592
- action: "setup" | "recon" | "author" | "implement" | "verify" | "ship" | "run";
592
+ action: "ship" | "setup" | "recon" | "author" | "implement" | "verify" | "run";
593
593
  state_path: string;
594
594
  stage: any;
595
595
  summary: string;
@@ -95,6 +95,88 @@ function updateState(statePath, mutate) {
95
95
  writeState(statePath, state);
96
96
  return state;
97
97
  }
98
+ var RUNTIME_EVENT_LIMIT = 100;
99
+ function nowIso() {
100
+ return (/* @__PURE__ */ new Date()).toISOString();
101
+ }
102
+ function appendRuntimeEventToState(state, event) {
103
+ const events = Array.isArray(state.runtime_events) ? state.runtime_events : [];
104
+ state.runtime_events = [...events, event].slice(-RUNTIME_EVENT_LIMIT);
105
+ state.runtime_updated_at = event.ts;
106
+ }
107
+ function beginRuntimeStep(statePath, action, step, workflowPath) {
108
+ const timer = {
109
+ startedAt: nowIso(),
110
+ startedMs: Date.now()
111
+ };
112
+ updateState(statePath, (state) => {
113
+ const current = {
114
+ step,
115
+ action,
116
+ status: "running",
117
+ started_at: timer.startedAt,
118
+ workflow_file: path.basename(workflowPath)
119
+ };
120
+ state.current_runtime_step = current;
121
+ appendRuntimeEventToState(state, {
122
+ ts: timer.startedAt,
123
+ kind: "workflow.step.started",
124
+ step,
125
+ action,
126
+ summary: `Started ${step} workflow step.`,
127
+ details: {
128
+ workflow_file: path.basename(workflowPath)
129
+ }
130
+ });
131
+ });
132
+ return timer;
133
+ }
134
+ function finishRuntimeStep(statePath, action, result, timer) {
135
+ const finishedAt = nowIso();
136
+ const durationMs = Date.now() - timer.startedMs;
137
+ const summary = result.haltedForApproval ? `${result.step} halted for approval.` : result.ok ? `Finished ${result.step} workflow step.` : `${result.step} workflow step failed.`;
138
+ updateState(statePath, (state) => {
139
+ const completed = {
140
+ step: result.step,
141
+ action,
142
+ status: result.haltedForApproval ? "approval_required" : result.ok ? "completed" : "failed",
143
+ started_at: timer.startedAt,
144
+ finished_at: finishedAt,
145
+ duration_ms: durationMs,
146
+ ok: result.ok,
147
+ halted_for_approval: result.haltedForApproval || false,
148
+ auto_approved: result.autoApproved || false,
149
+ error: result.error || null
150
+ };
151
+ state.current_runtime_step = null;
152
+ state.last_runtime_step = completed;
153
+ appendRuntimeEventToState(state, {
154
+ ts: finishedAt,
155
+ kind: "workflow.step.finished",
156
+ step: result.step,
157
+ action,
158
+ summary,
159
+ details: completed
160
+ });
161
+ });
162
+ return {
163
+ ...result,
164
+ started_at: timer.startedAt,
165
+ finished_at: finishedAt,
166
+ duration_ms: durationMs
167
+ };
168
+ }
169
+ function executedStep(res, extra = {}) {
170
+ const output = {
171
+ step: res.step,
172
+ ok: res.ok,
173
+ haltedForApproval: res.haltedForApproval || false,
174
+ autoApproved: res.autoApproved || false,
175
+ ...extra
176
+ };
177
+ if (typeof res.duration_ms === "number") output.duration_ms = res.duration_ms;
178
+ return output;
179
+ }
98
180
  function hasSupervisorProofAssessment(state) {
99
181
  const proofAssessment = state?.proof_assessment || {};
100
182
  const source = String(proofAssessment?.source || state?.proof_assessment_source || "").trim().toLowerCase();
@@ -557,53 +639,74 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
557
639
  const lobsterPrefix = process.env.RIDDLE_PROOF_LOBSTER_SCRIPT ? [process.env.RIDDLE_PROOF_LOBSTER_SCRIPT] : [];
558
640
  const runOne = (step) => {
559
641
  const args = step === "setup" ? buildSetupArgs(params, config) : {};
642
+ const stepWorkflowFile = workflowFile(config.riddleProofDir, step);
643
+ const timer = beginRuntimeStep(config.statePath, action, step, stepWorkflowFile);
560
644
  let output;
561
645
  try {
562
646
  output = JSON.parse(
563
- execFileSync(lobsterCommand, [...lobsterPrefix, "run", "--file", workflowFile(config.riddleProofDir, step), "--args-json", JSON.stringify(args)], {
647
+ execFileSync(lobsterCommand, [...lobsterPrefix, "run", "--file", stepWorkflowFile, "--args-json", JSON.stringify(args)], {
564
648
  encoding: "utf-8",
565
649
  env
566
650
  })
567
651
  );
568
652
  } catch (error) {
569
- return {
653
+ return finishRuntimeStep(config.statePath, action, {
570
654
  ok: false,
571
655
  step,
572
656
  error: error?.message || String(error),
573
657
  stdout: String(error?.stdout || ""),
574
658
  stderr: String(error?.stderr || "")
575
- };
659
+ }, timer);
576
660
  }
577
661
  if (output?.status === "needs_approval") {
578
662
  if (!params.auto_approve) {
579
- return {
663
+ return finishRuntimeStep(config.statePath, action, {
580
664
  ok: false,
581
665
  haltedForApproval: true,
582
666
  step,
583
667
  approval: output.requiresApproval || null,
584
668
  raw: output
585
- };
669
+ }, timer);
586
670
  }
587
671
  const token = output?.requiresApproval?.resumeToken;
588
- if (!token) throw new Error(`${step} requested approval without a resume token.`);
589
- const resumed = JSON.parse(
590
- execFileSync(lobsterCommand, [...lobsterPrefix, "resume", "--token", token, "--approve", "yes"], {
591
- encoding: "utf-8",
592
- env
593
- })
594
- );
595
- return {
672
+ if (!token) {
673
+ return finishRuntimeStep(config.statePath, action, {
674
+ ok: false,
675
+ step,
676
+ error: `${step} requested approval without a resume token.`,
677
+ raw: output
678
+ }, timer);
679
+ }
680
+ let resumed;
681
+ try {
682
+ resumed = JSON.parse(
683
+ execFileSync(lobsterCommand, [...lobsterPrefix, "resume", "--token", token, "--approve", "yes"], {
684
+ encoding: "utf-8",
685
+ env
686
+ })
687
+ );
688
+ } catch (error) {
689
+ return finishRuntimeStep(config.statePath, action, {
690
+ ok: false,
691
+ step,
692
+ autoApproved: true,
693
+ error: error?.message || String(error),
694
+ stdout: String(error?.stdout || ""),
695
+ stderr: String(error?.stderr || "")
696
+ }, timer);
697
+ }
698
+ return finishRuntimeStep(config.statePath, action, {
596
699
  ok: resumed?.ok !== false,
597
700
  step,
598
701
  autoApproved: true,
599
702
  raw: resumed
600
- };
703
+ }, timer);
601
704
  }
602
- return {
705
+ return finishRuntimeStep(config.statePath, action, {
603
706
  ok: output?.ok !== false,
604
707
  step,
605
708
  raw: output
606
- };
709
+ }, timer);
607
710
  };
608
711
  let effectiveAdvanceStage = params.advance_stage || null;
609
712
  const recordAttempt = (stage, status, summary, extra = {}) => {
@@ -729,7 +832,7 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
729
832
  let state = readState(config.statePath);
730
833
  if (!state || !state.workspace_ready || params.advance_stage === "setup") {
731
834
  const setupRes = runOne("setup");
732
- executed.push({ step: "setup", ok: setupRes.ok, haltedForApproval: setupRes.haltedForApproval || false, autoApproved: setupRes.autoApproved || false });
835
+ executed.push(executedStep(setupRes));
733
836
  if (!setupRes.ok || setupRes.haltedForApproval) {
734
837
  return failedRun("setup", setupRes.haltedForApproval ? "setup halted for approval" : "setup failed", setupRes, {
735
838
  checkpoint: "setup_blocked"
@@ -939,7 +1042,7 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
939
1042
  }
940
1043
  if (!state?.recon_results || state?.stage === "setup" || state?.stage === "preflight" || ["needs_agent_decision", "needs_supervisor_judgment"].includes(state?.recon_status || "") || requestedStage === "recon") {
941
1044
  const reconRes = runOne("recon");
942
- executed.push({ step: "recon", ok: reconRes.ok, haltedForApproval: reconRes.haltedForApproval || false, autoApproved: reconRes.autoApproved || false });
1045
+ executed.push(executedStep(reconRes));
943
1046
  if (!reconRes.ok || reconRes.haltedForApproval) {
944
1047
  return failedRun("recon", reconRes.haltedForApproval ? "recon halted for approval" : "recon failed", reconRes, {
945
1048
  checkpoint: "recon_failed",
@@ -987,7 +1090,7 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
987
1090
  state = readState(config.statePath);
988
1091
  if (!authorReady(state) || effectiveAdvanceStage === "author") {
989
1092
  const authorRes = runOne("author");
990
- executed.push({ step: "author", ok: authorRes.ok, haltedForApproval: authorRes.haltedForApproval || false, autoApproved: authorRes.autoApproved || false });
1093
+ executed.push(executedStep(authorRes));
991
1094
  if (!authorRes.ok || authorRes.haltedForApproval) {
992
1095
  return failedRun("author", authorRes.haltedForApproval ? "author halted for approval" : "author failed", authorRes, {
993
1096
  checkpoint: "author_failed",
@@ -1102,7 +1205,7 @@ async function executeWorkflow(params, pluginConfig, resolvedConfig) {
1102
1205
  }
1103
1206
  if (effectiveAdvanceStage === "implement") {
1104
1207
  const implementRes = runOne("implement");
1105
- executed.push({ step: "implement", ok: implementRes.ok, haltedForApproval: implementRes.haltedForApproval || false, autoApproved: implementRes.autoApproved || false });
1208
+ executed.push(executedStep(implementRes));
1106
1209
  if (implementRes.haltedForApproval) {
1107
1210
  return failedRun("implement", "implement halted for approval", implementRes, {
1108
1211
  checkpoint: "implement_blocked",
@@ -1192,7 +1295,7 @@ ${implementRes.stderr || ""}`;
1192
1295
  let verifyRes = { ok: true, step: "verify", reusedEvidence: canReuseVerifyEvidence };
1193
1296
  if (!canReuseVerifyEvidence) {
1194
1297
  verifyRes = runOne("verify");
1195
- executed.push({ step: "verify", ok: verifyRes.ok, haltedForApproval: verifyRes.haltedForApproval || false, autoApproved: verifyRes.autoApproved || false });
1298
+ executed.push(executedStep(verifyRes));
1196
1299
  if (!verifyRes.ok || verifyRes.haltedForApproval) {
1197
1300
  return failedRun("verify", verifyRes.haltedForApproval ? "verify halted for approval" : "verify failed", verifyRes, {
1198
1301
  checkpoint: "verify_failed",
@@ -1201,7 +1304,7 @@ ${implementRes.stderr || ""}`;
1201
1304
  });
1202
1305
  }
1203
1306
  } else {
1204
- executed.push({ step: "verify", ok: true, reusedEvidence: true, haltedForApproval: false, autoApproved: false });
1307
+ executed.push(executedStep(verifyRes, { reusedEvidence: true }));
1205
1308
  }
1206
1309
  state = readState(config.statePath);
1207
1310
  const verifyStatus = state?.verify_status || ((state?.after_cdn || "").trim() ? "evidence_captured" : "capture_incomplete");
@@ -1338,7 +1441,7 @@ ${implementRes.stderr || ""}`;
1338
1441
  details: { ...verifyDetails, shipGate }
1339
1442
  });
1340
1443
  const shipRes = runOne("ship");
1341
- executed.push({ step: "ship", ok: shipRes.ok, haltedForApproval: shipRes.haltedForApproval || false, autoApproved: shipRes.autoApproved || false });
1444
+ executed.push(executedStep(shipRes));
1342
1445
  if (!shipRes.ok || shipRes.haltedForApproval) {
1343
1446
  const shipNextAction = shipRes?.error && String(shipRes.error).includes("temporary proof branch") ? "product bug: ship resolved a temporary proof branch; resolve the PR head branch before retrying ship" : "inspect the ship error, confirm the PR head branch and verified commit, then retry ship";
1344
1447
  return failedRun("ship", shipRes.haltedForApproval ? "ship halted for approval" : "ship failed", shipRes, {
@@ -1517,7 +1620,7 @@ ${implementRes.stderr || ""}`;
1517
1620
  return shipGateBlocked(state, executed, { shipAssessment: shipAssessment.raw });
1518
1621
  }
1519
1622
  const shipRes = runOne("ship");
1520
- executed.push({ step: "ship", ok: shipRes.ok, haltedForApproval: shipRes.haltedForApproval || false, autoApproved: shipRes.autoApproved || false });
1623
+ executed.push(executedStep(shipRes));
1521
1624
  if (!shipRes.ok || shipRes.haltedForApproval) {
1522
1625
  return failedRun("ship", shipRes.haltedForApproval ? "ship halted for approval" : "ship failed", shipRes, {
1523
1626
  checkpoint: "ship_failed",
@@ -318,6 +318,11 @@ function writeDepsManifest(projectDir, fingerprint, installCmd) {
318
318
  writeFileSync(manifestPath, JSON.stringify({ fingerprint, install_cmd: installCmd }, null, 2));
319
319
  }
320
320
 
321
+ function dependencyInstallTimeoutMs() {
322
+ const parsed = Number.parseInt(process.env.RIDDLE_PROOF_INSTALL_TIMEOUT_MS || "", 10);
323
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 600000;
324
+ }
325
+
321
326
  export function ensureDeps({ projectDir, reuseFrom = "" } = {}) {
322
327
  const fingerprint = computeDependencyFingerprint(projectDir);
323
328
  if (!fingerprint) return "no_package_json";
@@ -341,7 +346,7 @@ export function ensureDeps({ projectDir, reuseFrom = "" } = {}) {
341
346
 
342
347
  const installCmd = detectInstallCommand(projectDir);
343
348
  if (!installCmd) return "no_install_command";
344
- const installResult = runSafe(`${installCmd} 2>&1 | tail -5`, projectDir, 300000);
349
+ const installResult = runSafe(`${installCmd} 2>&1 | tail -5`, projectDir, dependencyInstallTimeoutMs());
345
350
  if (!installResult.ok) {
346
351
  throw new Error(`dependency install failed in ${projectDir}: ${installResult.output.slice(0, 300)}`);
347
352
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riddledc/riddle-proof",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Reusable Riddle Proof contracts and helpers for evidence-backed agent changes.",
5
5
  "license": "MIT",
6
6
  "author": "RiddleDC",
@@ -239,6 +239,19 @@ if missing:
239
239
  print('MISSING: ' + ', '.join(missing))
240
240
  print('=' * 50)
241
241
 
242
+ # The TypeScript harness writes runtime observability fields before Lobster
243
+ # starts. Preflight initializes the main state file, so preserve those fields
244
+ # rather than making status polling go blind during setup.
245
+ if os.path.exists(STATE_FILE):
246
+ try:
247
+ with open(STATE_FILE) as existing_state_file:
248
+ existing_state = json.load(existing_state_file)
249
+ except Exception:
250
+ existing_state = {}
251
+ for runtime_key in ('current_runtime_step', 'last_runtime_step', 'runtime_events', 'runtime_updated_at'):
252
+ if runtime_key in existing_state:
253
+ s[runtime_key] = existing_state[runtime_key]
254
+
242
255
  save_state(s)
243
256
 
244
257
  if missing:
@@ -6,7 +6,7 @@ workspace root by default:
6
6
  <workspace>/.riddle-proof-worktrees/riddle-proof-<run_id>-after
7
7
  """
8
8
 
9
- import json, subprocess as sp, os, sys, shutil
9
+ import json, subprocess as sp, os, sys, shutil, time
10
10
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
11
11
  from util import load_state, save_state, git, shell_quote
12
12
 
@@ -37,6 +37,15 @@ SKILLS_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__fil
37
37
  WORKSPACE_CORE = os.path.join(SKILLS_ROOT, 'lib', 'workspace-core.mjs')
38
38
 
39
39
 
40
+ def dependency_timeout_seconds():
41
+ raw = os.environ.get('RIDDLE_PROOF_INSTALL_TIMEOUT_MS', '').strip()
42
+ try:
43
+ millis = int(raw)
44
+ except Exception:
45
+ millis = 600000
46
+ return max(660, int((millis + 999) / 1000) + 30)
47
+
48
+
40
49
  def workspace_core(command, payload, timeout=180):
41
50
  if not os.path.exists(WORKSPACE_CORE):
42
51
  raise SystemExit('workspace core helper missing: ' + WORKSPACE_CORE)
@@ -64,10 +73,64 @@ def ensure_deps(project_dir, reuse_from=''):
64
73
  payload = {'projectDir': project_dir}
65
74
  if reuse_from:
66
75
  payload['reuseFrom'] = reuse_from
67
- result = workspace_core('ensure-deps', payload, timeout=300)
76
+ result = workspace_core('ensure-deps', payload, timeout=dependency_timeout_seconds())
68
77
  return result.get('status', '')
69
78
 
70
79
 
80
+ def record_setup_phase(phase, status='running', summary=''):
81
+ global s
82
+ ts = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
83
+ try:
84
+ current = load_state()
85
+ except Exception:
86
+ current = dict(s)
87
+ runtime_step = current.get('current_runtime_step') if isinstance(current.get('current_runtime_step'), dict) else {}
88
+ if not runtime_step:
89
+ runtime_step = {
90
+ 'step': 'setup',
91
+ 'action': 'run',
92
+ 'status': 'running',
93
+ 'started_at': ts,
94
+ 'workflow_file': 'riddle-proof-setup.lobster',
95
+ }
96
+ runtime_step['phase'] = phase
97
+ runtime_step['phase_status'] = status
98
+ if status == 'running':
99
+ runtime_step['phase_started_at'] = ts
100
+ runtime_step.pop('phase_finished_at', None)
101
+ else:
102
+ runtime_step['phase_finished_at'] = ts
103
+ if summary:
104
+ runtime_step['summary'] = summary
105
+ current['current_runtime_step'] = runtime_step
106
+ events = current.get('runtime_events') if isinstance(current.get('runtime_events'), list) else []
107
+ events.append({
108
+ 'ts': ts,
109
+ 'kind': 'workflow.phase.' + ('started' if status == 'running' else 'finished'),
110
+ 'step': 'setup',
111
+ 'phase': phase,
112
+ 'summary': summary or (phase + ' ' + status),
113
+ 'details': {'status': status},
114
+ })
115
+ current['runtime_events'] = events[-100:]
116
+ current['runtime_updated_at'] = ts
117
+ save_state(current)
118
+ for key in ('current_runtime_step', 'runtime_events', 'runtime_updated_at'):
119
+ if key in current:
120
+ s[key] = current[key]
121
+
122
+
123
+ def ensure_deps_phase(phase, project_dir, reuse_from='', summary=''):
124
+ record_setup_phase(phase, 'running', summary)
125
+ try:
126
+ status = ensure_deps(project_dir, reuse_from=reuse_from)
127
+ except BaseException as exc:
128
+ record_setup_phase(phase, 'failed', str(exc)[:300])
129
+ raise
130
+ record_setup_phase(phase, 'completed', status or 'no dependency install needed')
131
+ return status
132
+
133
+
71
134
  def resolve_worktree_root(repo_dir):
72
135
  configured = (s.get('worktree_root') or os.environ.get('RIDDLE_PROOF_WORKTREE_ROOT') or '').strip()
73
136
  if configured:
@@ -339,16 +402,16 @@ apply_repo_profile(AFTER_DIR)
339
402
  save_state(s)
340
403
 
341
404
  reuse_source = repo_dir if os.path.exists(os.path.join(repo_dir, 'package.json')) else ''
342
- shared_status = ensure_deps(reuse_source) if reuse_source else ''
405
+ shared_status = ensure_deps_phase('shared_deps', reuse_source, summary='Ensuring shared repository dependencies.') if reuse_source else ''
343
406
  if shared_status:
344
407
  print('Shared deps status: ' + shared_status)
345
408
 
346
409
  before_dep_status = ''
347
410
  if reference in ('before', 'both'):
348
- before_dep_status = ensure_deps(BEFORE_DIR, reuse_from=reuse_source)
411
+ before_dep_status = ensure_deps_phase('before_deps', BEFORE_DIR, reuse_from=reuse_source, summary='Ensuring before-worktree dependencies.')
349
412
  print('Before deps status: ' + before_dep_status)
350
413
 
351
- after_dep_status = ensure_deps(AFTER_DIR, reuse_from=reuse_source)
414
+ after_dep_status = ensure_deps_phase('after_deps', AFTER_DIR, reuse_from=reuse_source, summary='Ensuring after-worktree dependencies.')
352
415
  print('After deps status: ' + after_dep_status)
353
416
 
354
417
  # Patch Next.js config in after worktree if needed