erdos-problems 0.1.3 → 0.1.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.
package/README.md CHANGED
@@ -20,6 +20,7 @@ Official binary:
20
20
  - bundled upstream snapshot from `teorth/erdosproblems`
21
21
  - workspace `.erdos/` state for active-problem selection, upstream refreshes, reports, scaffolds, and pull bundles
22
22
  - sunflower cluster as the first deep harness pack
23
+ - packaged compute-lane metadata for deep sunflower problems, surfaced directly in the CLI
23
24
  - seeded atlas now includes open and solved problems beyond sunflower
24
25
  - unseeded problems can still be pulled into a workspace from the bundled upstream snapshot
25
26
 
@@ -72,6 +73,8 @@ erdos problem artifacts 857 --json
72
73
  erdos cluster list
73
74
  erdos cluster show sunflower
74
75
  erdos workspace show
76
+ erdos sunflower status 857
77
+ erdos sunflower status --json
75
78
  erdos dossier show
76
79
  erdos upstream show
77
80
  erdos upstream sync
@@ -107,6 +110,11 @@ The CLI can surface these directly:
107
110
  - `erdos bootstrap problem <id>` selects the problem and creates the scaffold in one step
108
111
  - `erdos pull problem <id>` creates a workspace bundle for any problem in the upstream snapshot
109
112
 
113
+ For deep sunflower problems, the CLI also surfaces packaged compute lanes:
114
+ - `erdos sunflower status <id>` shows route + compute posture together
115
+ - `erdos workspace show` includes the active sunflower compute summary when applicable
116
+ - `erdos scaffold problem <id>` copies packaged compute packets into `.erdos/scaffolds/<id>/COMPUTE/`
117
+
110
118
  ## Notes
111
119
 
112
120
  - `erdos-problems` is the canonical npm package name.
@@ -209,6 +209,7 @@ And also:
209
209
  - gates
210
210
  - atoms
211
211
  - ready queue
212
+ - compute lane awareness
212
213
  - generated checkpoints
213
214
  - literature mapping
214
215
 
@@ -240,8 +241,18 @@ erdos sunflower setup 857
240
241
  erdos sunflower warnings 857
241
242
  erdos sunflower pass 857
242
243
  erdos sunflower frontier 857
244
+ erdos sunflower status 857
243
245
  ```
244
246
 
247
+ `erdos sunflower status` should be the public-shell place where route state and
248
+ compute posture meet:
249
+
250
+ - active route
251
+ - breakthrough state
252
+ - packaged compute lane
253
+ - next compute action
254
+ - whether paid approval is required
255
+
245
256
  ## Content Policy
246
257
 
247
258
  The repo should not blindly mirror `erdosproblems.com` text.
@@ -281,4 +292,3 @@ This keeps the repo publishable and reduces licensing ambiguity.
281
292
  The first public promise should be:
282
293
 
283
294
  > `erdos-problems` is a CLI atlas and staged research harness for Paul Erdős problems, with the sunflower family as the first deeply integrated pack.
284
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "erdos-problems",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI atlas and staged research harness for Paul Erdos problems.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,26 @@
1
+ lane_id: m8_exactness_cube_and_certificate_v0
2
+ problem_id: "857"
3
+ cluster: sunflower
4
+ question: "Is M(8,3) = 45 exact, equivalently is the target-46 SAT instance UNSAT?"
5
+ claim_level_goal: Exact
6
+ status: ready_for_local_scout
7
+ price_checked_local_date: "2026-03-25"
8
+ recommendation: cpu_first
9
+ approval_required: true
10
+ summary: "Packaged sunflower compute lane for the first exact M(8,3) certification packet. The next honest move is the local scout, not paid launch."
11
+ source_repo:
12
+ name: sunflower-coda
13
+ canonical_packet_path: analysis/compute/m8_exactness_cube_and_certificate_v0/launch_packet.yaml
14
+ public_feature:
15
+ command: erdos sunflower status 857
16
+ scaffold_subdir: COMPUTE
17
+ rungs:
18
+ - name: local_scout
19
+ role: local_unmetered
20
+ note: "Run the local scout packet first and require clean artifacts before any paid rung."
21
+ - name: cpu_transfer
22
+ role: paid_metered
23
+ note: "Preferred first paid rung once the local scout has earned promotion."
24
+ - name: heuristic_gpu_sidecar
25
+ role: paid_metered
26
+ note: "Optional and heuristic-only; it supports the exact lane but does not upgrade claims by itself."
package/src/cli/index.js CHANGED
@@ -4,6 +4,7 @@ import { runDossierCommand } from '../commands/dossier.js';
4
4
  import { runProblemCommand } from '../commands/problem.js';
5
5
  import { runPullCommand } from '../commands/pull.js';
6
6
  import { runScaffoldCommand } from '../commands/scaffold.js';
7
+ import { runSunflowerCommand } from '../commands/sunflower.js';
7
8
  import { runUpstreamCommand } from '../commands/upstream.js';
8
9
  import { runWorkspaceCommand } from '../commands/workspace.js';
9
10
 
@@ -19,6 +20,7 @@ function printUsage() {
19
20
  console.log(' erdos cluster list');
20
21
  console.log(' erdos cluster show <name>');
21
22
  console.log(' erdos workspace show');
23
+ console.log(' erdos sunflower status [<id>] [--json]');
22
24
  console.log(' erdos dossier show <id>');
23
25
  console.log(' erdos upstream show');
24
26
  console.log(' erdos upstream sync [--write-package-snapshot]');
@@ -41,6 +43,8 @@ if (!command || command === 'help' || command === '--help') {
41
43
  exitCode = runClusterCommand(rest);
42
44
  } else if (command === 'workspace') {
43
45
  exitCode = runWorkspaceCommand(rest);
46
+ } else if (command === 'sunflower') {
47
+ exitCode = runSunflowerCommand(rest);
44
48
  } else if (command === 'dossier') {
45
49
  exitCode = runDossierCommand(rest);
46
50
  } else if (command === 'upstream') {
@@ -129,6 +129,12 @@ function printArtifactInventory(problem, inventory, asJson) {
129
129
  if (inventory.packContext) {
130
130
  console.log(`- ${inventory.packContext.label}: ${inventory.packContext.exists ? 'present' : 'missing'} (${inventory.packContext.path})`);
131
131
  }
132
+ if (inventory.computePackets.length > 0) {
133
+ console.log('Compute packets:');
134
+ for (const packet of inventory.computePackets) {
135
+ console.log(`- ${packet.label}: ${packet.exists ? 'present' : 'missing'} (${packet.path}) [${packet.status}]`);
136
+ }
137
+ }
132
138
  if (inventory.upstreamSnapshot) {
133
139
  console.log('Upstream snapshot:');
134
140
  console.log(`- kind: ${inventory.upstreamSnapshot.kind}`);
@@ -0,0 +1,97 @@
1
+ import { getProblem } from '../atlas/catalog.js';
2
+ import { getWorkspaceRoot } from '../runtime/paths.js';
3
+ import { buildSunflowerStatusSnapshot, writeSunflowerStatusRecord } from '../runtime/sunflower.js';
4
+ import { readCurrentProblem } from '../runtime/workspace.js';
5
+
6
+ function parseStatusArgs(args) {
7
+ const parsed = {
8
+ problemId: null,
9
+ asJson: false,
10
+ };
11
+
12
+ for (let index = 0; index < args.length; index += 1) {
13
+ const token = args[index];
14
+ if (token === '--json') {
15
+ parsed.asJson = true;
16
+ continue;
17
+ }
18
+ if (!parsed.problemId) {
19
+ parsed.problemId = token;
20
+ continue;
21
+ }
22
+ return { error: `Unknown sunflower status option: ${token}` };
23
+ }
24
+
25
+ return parsed;
26
+ }
27
+
28
+ function printSunflowerStatus(snapshot, registryPaths) {
29
+ console.log(`${snapshot.displayName} sunflower harness`);
30
+ console.log(`Title: ${snapshot.title}`);
31
+ console.log(`Active route: ${snapshot.activeRoute ?? '(none)'}`);
32
+ console.log(`Route breakthrough: ${snapshot.routeBreakthrough ? 'yes' : 'no'}`);
33
+ console.log(`Open problem: ${snapshot.openProblem ? 'yes' : 'no'}`);
34
+ console.log(`Problem solved: ${snapshot.problemSolved ? 'yes' : 'no'}`);
35
+ console.log(`Compute lane present: ${snapshot.computeLanePresent ? 'yes' : 'no'}`);
36
+ console.log(`Compute lane count: ${snapshot.computeLaneCount}`);
37
+ console.log(`Compute summary: ${snapshot.computeSummary}`);
38
+ console.log(`Compute next: ${snapshot.computeNextAction}`);
39
+ if (snapshot.activePacket) {
40
+ console.log(`Compute lane: ${snapshot.activePacket.laneId} [${snapshot.activePacket.status}]`);
41
+ console.log(`Claim level goal: ${snapshot.activePacket.claimLevelGoal}`);
42
+ console.log(`Recommendation: ${snapshot.activePacket.recommendation || '(none)'}`);
43
+ console.log(`Approval required: ${snapshot.activePacket.approvalRequired ? 'yes' : 'no'}`);
44
+ console.log(`Price checked: ${snapshot.activePacket.priceCheckedLocalDate || '(unknown)'}`);
45
+ console.log(`Packet file: ${snapshot.activePacket.packetFileName}`);
46
+ }
47
+ console.log(`Registry record: ${registryPaths.latestPath}`);
48
+ }
49
+
50
+ export function runSunflowerCommand(args) {
51
+ const [subcommand, ...rest] = args;
52
+
53
+ if (!subcommand || subcommand === 'help' || subcommand === '--help') {
54
+ console.log('Usage:');
55
+ console.log(' erdos sunflower status [<id>] [--json]');
56
+ return 0;
57
+ }
58
+
59
+ if (subcommand !== 'status') {
60
+ console.error(`Unknown sunflower subcommand: ${subcommand}`);
61
+ return 1;
62
+ }
63
+
64
+ const parsed = parseStatusArgs(rest);
65
+ if (parsed.error) {
66
+ console.error(parsed.error);
67
+ return 1;
68
+ }
69
+
70
+ const problemId = parsed.problemId ?? readCurrentProblem();
71
+ if (!problemId) {
72
+ console.error('Missing problem id and no active problem is selected.');
73
+ return 1;
74
+ }
75
+
76
+ const problem = getProblem(problemId);
77
+ if (!problem) {
78
+ console.error(`Unknown problem: ${problemId}`);
79
+ return 1;
80
+ }
81
+
82
+ if (problem.cluster !== 'sunflower') {
83
+ console.error(`Problem ${problem.problemId} is not in the sunflower harness.`);
84
+ return 1;
85
+ }
86
+
87
+ const snapshot = buildSunflowerStatusSnapshot(problem);
88
+ const registryPaths = writeSunflowerStatusRecord(problem, snapshot, getWorkspaceRoot());
89
+
90
+ if (parsed.asJson) {
91
+ console.log(JSON.stringify({ ...snapshot, registryPaths }, null, 2));
92
+ return 0;
93
+ }
94
+
95
+ printSunflowerStatus(snapshot, registryPaths);
96
+ return 0;
97
+ }
@@ -1,3 +1,5 @@
1
+ import { getProblem } from '../atlas/catalog.js';
2
+ import { buildSunflowerStatusSnapshot } from '../runtime/sunflower.js';
1
3
  import { getWorkspaceSummary } from '../runtime/workspace.js';
2
4
 
3
5
  export function runWorkspaceCommand(args) {
@@ -23,5 +25,17 @@ export function runWorkspaceCommand(args) {
23
25
  console.log(`Workspace scaffold dir: ${summary.scaffoldDir}`);
24
26
  console.log(`Workspace pull dir: ${summary.pullDir}`);
25
27
  console.log(`Updated at: ${summary.updatedAt ?? '(none)'}`);
28
+ if (summary.activeProblem) {
29
+ const problem = getProblem(summary.activeProblem);
30
+ if (problem?.cluster === 'sunflower') {
31
+ const sunflower = buildSunflowerStatusSnapshot(problem);
32
+ console.log(`Sunflower route: ${sunflower.activeRoute ?? '(none)'}`);
33
+ console.log(`Sunflower compute: ${sunflower.computeLanePresent ? 'yes' : 'no'}`);
34
+ if (sunflower.activePacket) {
35
+ console.log(`Sunflower compute lane: ${sunflower.activePacket.laneId} [${sunflower.activePacket.status}]`);
36
+ }
37
+ console.log(`Sunflower compute next: ${sunflower.computeNextAction}`);
38
+ }
39
+ }
26
40
  return 0;
27
41
  }
@@ -12,6 +12,14 @@ export function getWorkspaceDir() {
12
12
  return path.join(getWorkspaceRoot(), '.erdos');
13
13
  }
14
14
 
15
+ export function getWorkspaceRegistryDir(workspaceRoot = getWorkspaceRoot()) {
16
+ return path.join(workspaceRoot, '.erdos', 'registry');
17
+ }
18
+
19
+ export function getWorkspaceComputeRegistryDir(workspaceRoot = getWorkspaceRoot()) {
20
+ return path.join(getWorkspaceRegistryDir(workspaceRoot), 'compute');
21
+ }
22
+
15
23
  export function getWorkspaceStatePath() {
16
24
  return path.join(getWorkspaceDir(), 'state.json');
17
25
  }
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { loadActiveUpstreamSnapshot } from '../upstream/sync.js';
4
4
  import { copyFileIfPresent, ensureDir, writeJson, writeText } from './files.js';
5
5
  import { getPackDir } from './paths.js';
6
+ import { listSunflowerComputePackets } from './sunflower.js';
6
7
 
7
8
  const DOSSIER_FILES = [
8
9
  ['problem.yaml', 'problemYamlPath', 'problem.yaml'],
@@ -42,6 +43,15 @@ export function getProblemArtifactInventory(problem) {
42
43
  }
43
44
  : null;
44
45
 
46
+ const computePackets = listSunflowerComputePackets(problem.problemId).map((packet) => ({
47
+ label: packet.laneId || packet.packetFileName,
48
+ path: packet.packetPath,
49
+ destinationName: packet.packetFileName,
50
+ exists: fs.existsSync(packet.packetPath),
51
+ status: packet.status,
52
+ claimLevelGoal: packet.claimLevelGoal,
53
+ }));
54
+
45
55
  return {
46
56
  generatedAt: new Date().toISOString(),
47
57
  problemId: problem.problemId,
@@ -54,6 +64,7 @@ export function getProblemArtifactInventory(problem) {
54
64
  problemDir: problem.problemDir,
55
65
  canonicalArtifacts,
56
66
  packContext,
67
+ computePackets,
57
68
  upstreamSnapshot: snapshot
58
69
  ? {
59
70
  kind: snapshot.kind,
@@ -96,6 +107,24 @@ export function scaffoldProblem(problem, destination) {
96
107
  }
97
108
  }
98
109
 
110
+ const computeArtifacts = [];
111
+ const computeDir = path.join(destination, 'COMPUTE');
112
+ for (const packet of inventory.computePackets) {
113
+ if (!packet.exists) {
114
+ continue;
115
+ }
116
+ const destinationPath = path.join(computeDir, packet.destinationName);
117
+ if (copyFileIfPresent(packet.path, destinationPath)) {
118
+ computeArtifacts.push({
119
+ label: packet.label,
120
+ sourcePath: packet.path,
121
+ destinationPath,
122
+ status: packet.status,
123
+ claimLevelGoal: packet.claimLevelGoal,
124
+ });
125
+ }
126
+ }
127
+
99
128
  if (inventory.upstreamRecord) {
100
129
  writeJson(path.join(destination, 'UPSTREAM_RECORD.json'), inventory.upstreamRecord);
101
130
  }
@@ -117,8 +146,10 @@ export function scaffoldProblem(problem, destination) {
117
146
  problemId: problem.problemId,
118
147
  bundledProblemDir: problem.problemDir,
119
148
  copiedArtifacts,
149
+ computeArtifacts,
120
150
  packContext: inventory.packContext,
121
151
  canonicalArtifacts: inventory.canonicalArtifacts,
152
+ computePackets: inventory.computePackets,
122
153
  upstreamSnapshot: inventory.upstreamSnapshot,
123
154
  includedUpstreamRecord: inventory.upstreamRecordIncluded,
124
155
  };
@@ -138,6 +169,7 @@ export function scaffoldProblem(problem, destination) {
138
169
  `- Repo status: ${problem.repoStatus}`,
139
170
  `- Harness depth: ${problem.harnessDepth}`,
140
171
  `- Upstream record included: ${inventory.upstreamRecordIncluded ? 'yes' : 'no'}`,
172
+ `- Compute packets copied: ${computeArtifacts.length}`,
141
173
  '',
142
174
  ].join('\n'),
143
175
  );
@@ -0,0 +1,208 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parse } from 'yaml';
4
+ import { writeJson } from './files.js';
5
+ import { getPackDir, getWorkspaceComputeRegistryDir } from './paths.js';
6
+
7
+ const CLAIM_LEVEL_PRIORITY = {
8
+ Exact: 4,
9
+ Verified: 3,
10
+ Heuristic: 2,
11
+ Conjecture: 1,
12
+ };
13
+
14
+ const STATUS_PRIORITY = {
15
+ paid_active: 7,
16
+ local_scout_running: 6,
17
+ ready_for_paid_transfer: 5,
18
+ ready_for_local_scout: 4,
19
+ active: 3,
20
+ blocked: 2,
21
+ complete: 1,
22
+ unknown: 0,
23
+ };
24
+
25
+ function getSunflowerComputeDir(problemId) {
26
+ return path.join(getPackDir('sunflower'), 'compute', String(problemId));
27
+ }
28
+
29
+ function readComputePacket(packetPath) {
30
+ const parsed = parse(fs.readFileSync(packetPath, 'utf8')) ?? {};
31
+ return {
32
+ laneId: String(parsed.lane_id ?? '').trim(),
33
+ problemId: String(parsed.problem_id ?? '').trim(),
34
+ cluster: String(parsed.cluster ?? 'sunflower').trim(),
35
+ question: String(parsed.question ?? '').trim(),
36
+ claimLevelGoal: String(parsed.claim_level_goal ?? '').trim(),
37
+ status: String(parsed.status ?? 'unknown').trim() || 'unknown',
38
+ priceCheckedLocalDate: String(parsed.price_checked_local_date ?? '').trim(),
39
+ recommendation: String(parsed.recommendation ?? '').trim(),
40
+ approvalRequired: Boolean(parsed.approval_required),
41
+ summary: String(parsed.summary ?? '').trim(),
42
+ packetPath,
43
+ packetFileName: path.basename(packetPath),
44
+ sourceRepo: parsed.source_repo ?? null,
45
+ publicFeature: parsed.public_feature ?? null,
46
+ rungs: Array.isArray(parsed.rungs) ? parsed.rungs : [],
47
+ };
48
+ }
49
+
50
+ export function listSunflowerComputePackets(problemId) {
51
+ const computeDir = getSunflowerComputeDir(problemId);
52
+ if (!fs.existsSync(computeDir)) {
53
+ return [];
54
+ }
55
+
56
+ return fs
57
+ .readdirSync(computeDir)
58
+ .filter((entry) => entry.endsWith('.yaml') || entry.endsWith('.yml'))
59
+ .sort()
60
+ .map((entry) => readComputePacket(path.join(computeDir, entry)));
61
+ }
62
+
63
+ function chooseActivePacket(packets) {
64
+ if (packets.length === 0) {
65
+ return null;
66
+ }
67
+
68
+ const ranked = [...packets].sort((left, right) => {
69
+ const statusDelta =
70
+ (STATUS_PRIORITY[right.status] ?? STATUS_PRIORITY.unknown)
71
+ - (STATUS_PRIORITY[left.status] ?? STATUS_PRIORITY.unknown);
72
+ if (statusDelta !== 0) {
73
+ return statusDelta;
74
+ }
75
+ const claimDelta =
76
+ (CLAIM_LEVEL_PRIORITY[right.claimLevelGoal] ?? 0)
77
+ - (CLAIM_LEVEL_PRIORITY[left.claimLevelGoal] ?? 0);
78
+ if (claimDelta !== 0) {
79
+ return claimDelta;
80
+ }
81
+ return right.laneId.localeCompare(left.laneId);
82
+ });
83
+
84
+ return ranked[0];
85
+ }
86
+
87
+ function deriveSummary(packet) {
88
+ if (!packet) {
89
+ return {
90
+ computeSummary: 'No packaged compute lane is registered for this sunflower problem yet.',
91
+ computeNextAction: 'Stay in the atlas/dossier lane until a frozen benchmark and artifact bundle exist.',
92
+ budgetState: 'not_applicable',
93
+ };
94
+ }
95
+
96
+ const budgetState = packet.approvalRequired ? 'approval_required' : 'not_required';
97
+ if (packet.summary) {
98
+ return {
99
+ computeSummary: packet.summary,
100
+ computeNextAction:
101
+ packet.status === 'ready_for_local_scout'
102
+ ? 'Run the local scout first, then decide whether paid compute is honestly earned.'
103
+ : packet.status === 'ready_for_paid_transfer'
104
+ ? 'If budget approval is granted, launch the preferred paid rung and mirror the artifacts back into the workspace.'
105
+ : packet.status === 'paid_active'
106
+ ? 'Let the active metered run finish and pull back logs, artifacts, and impact notes before upgrading any claim.'
107
+ : packet.status === 'complete'
108
+ ? 'Review the completed artifact bundle and decide whether the next move is replay, certificate assembly, or a new frozen lane.'
109
+ : 'Refresh the compute lane status before making it part of the next route decision.',
110
+ budgetState,
111
+ };
112
+ }
113
+
114
+ if (packet.status === 'ready_for_local_scout') {
115
+ return {
116
+ computeSummary: `${packet.laneId} is packaged and ready for its local scout.`,
117
+ computeNextAction: 'Run the local scout first, then decide whether paid compute is honestly earned.',
118
+ budgetState,
119
+ };
120
+ }
121
+
122
+ if (packet.status === 'ready_for_paid_transfer') {
123
+ return {
124
+ computeSummary: `${packet.laneId} cleared the scout and is ready for the ${packet.recommendation || 'paid'} rung.`,
125
+ computeNextAction: 'If budget approval is granted, launch the preferred paid rung and mirror the artifacts back into the workspace.',
126
+ budgetState,
127
+ };
128
+ }
129
+
130
+ if (packet.status === 'paid_active') {
131
+ return {
132
+ computeSummary: `${packet.laneId} is actively using metered compute.`,
133
+ computeNextAction: 'Let the active metered run finish and pull back logs, artifacts, and impact notes before upgrading any claim.',
134
+ budgetState,
135
+ };
136
+ }
137
+
138
+ if (packet.status === 'complete') {
139
+ return {
140
+ computeSummary: `${packet.laneId} has a completed compute packet.`,
141
+ computeNextAction: 'Review the completed artifact bundle and decide whether the next move is replay, certificate assembly, or a new frozen lane.',
142
+ budgetState,
143
+ };
144
+ }
145
+
146
+ return {
147
+ computeSummary: `${packet.laneId} is packaged with status ${packet.status}.`,
148
+ computeNextAction: 'Refresh the compute lane status before making it part of the next route decision.',
149
+ budgetState,
150
+ };
151
+ }
152
+
153
+ function compactPacket(packet) {
154
+ if (!packet) {
155
+ return null;
156
+ }
157
+
158
+ return {
159
+ laneId: packet.laneId,
160
+ status: packet.status,
161
+ claimLevelGoal: packet.claimLevelGoal,
162
+ question: packet.question,
163
+ recommendation: packet.recommendation,
164
+ approvalRequired: packet.approvalRequired,
165
+ priceCheckedLocalDate: packet.priceCheckedLocalDate,
166
+ packetFileName: packet.packetFileName,
167
+ sourceRepo: packet.sourceRepo,
168
+ };
169
+ }
170
+
171
+ export function buildSunflowerStatusSnapshot(problem) {
172
+ const packets = listSunflowerComputePackets(problem.problemId);
173
+ const activePacket = chooseActivePacket(packets);
174
+ const summary = deriveSummary(activePacket);
175
+
176
+ return {
177
+ generatedAt: new Date().toISOString(),
178
+ problemId: problem.problemId,
179
+ displayName: problem.displayName,
180
+ title: problem.title,
181
+ cluster: problem.cluster,
182
+ activeRoute: problem.researchState?.active_route ?? null,
183
+ routeBreakthrough: Boolean(problem.researchState?.route_breakthrough),
184
+ openProblem: Boolean(problem.researchState?.open_problem),
185
+ problemSolved: Boolean(problem.researchState?.problem_solved),
186
+ computeLanePresent: Boolean(activePacket),
187
+ computeLaneCount: packets.length,
188
+ computeSummary: summary.computeSummary,
189
+ computeNextAction: summary.computeNextAction,
190
+ budgetState: summary.budgetState,
191
+ activePacket: compactPacket(activePacket),
192
+ computePackets: packets.map((packet) => compactPacket(packet)),
193
+ };
194
+ }
195
+
196
+ export function writeSunflowerStatusRecord(problem, snapshot, workspaceRoot) {
197
+ const registryDir = getWorkspaceComputeRegistryDir(workspaceRoot);
198
+ const timestamp = new Date().toISOString().replaceAll(':', '-');
199
+ const timestampedPath = path.join(registryDir, `${timestamp}__p${problem.problemId}.json`);
200
+ const latestPath = path.join(registryDir, `latest__p${problem.problemId}.json`);
201
+ writeJson(timestampedPath, snapshot);
202
+ writeJson(latestPath, snapshot);
203
+ return {
204
+ registryDir,
205
+ timestampedPath,
206
+ latestPath,
207
+ };
208
+ }