Accept soft direct Scattermind lane sections

This commit is contained in:
OpenClaw Bot
2026-05-27 15:50:52 +02:00
parent f7d459a629
commit 10084afb96
4 changed files with 85 additions and 15 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ Candidate items may include optional 110 `rankerHints` (`value`, `effort`, `c
Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as `concept-map.buildOrder.validateNext#1` and carry the original string as `sourceQuote`, so simple Scattermind exports stay addressable downstream instead of becoming anonymous `feature-1` rows. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` / `handoff.source.originalPromptExcerpt` or, when the original prompt is unavailable, `sourceSummaryExcerpt` carry the source context so a downstream Scattermind handoff can show why the build order exists without digging through `input.provenance`. Scattermind should use these when a next move came from a specific Concept Map lens sentence, so Ranker can defend not just what wins but where the judgement came from. Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as `concept-map.buildOrder.validateNext#1` and carry the original string as `sourceQuote`, so simple Scattermind exports stay addressable downstream instead of becoming anonymous `feature-1` rows. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` / `handoff.source.originalPromptExcerpt` or, when the original prompt is unavailable, `sourceSummaryExcerpt` carry the source context so a downstream Scattermind handoff can show why the build order exists without digging through `input.provenance`. Scattermind should use these when a next move came from a specific Concept Map lens sentence, so Ranker can defend not just what wins but where the judgement came from.
Soft Scattermind labels are accepted at the bridge boundary so Scattermind does not need to use harsh verdict copy in its own product surface. Lens text can say `Continue first`, `Make tangible`, `Try next`, `Evidence next`, `Hold for later`, or `Set aside`; Build Order objects can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `holdForLater`, and `setAside`. Ranker maps those to `doFirst / validateNext / defer / park` while preserving the softer original label in `sourceQuote`. Soft Scattermind labels are accepted at the bridge boundary so Scattermind does not need to use harsh verdict copy in its own product surface. Lens text can say `Continue first`, `Make tangible`, `Try next`, `Evidence next`, `Hold for later`, or `Set aside`; Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `holdForLater`, and `setAside`. Ranker maps those to `doFirst / validateNext / defer / park` while preserving the softer original label in `sourceQuote` or candidate source trace.
Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. Handoff `readiness` now gives downstream bridge consumers a deterministic gate: `ready`, `usable-with-warnings`, `needs-source-context`, or `blocked`, with blockers and next checks for missing evidence, source trace, duplicate IDs, or active source-non-goal conflicts. For low-friction handoff, `/api/rank-feedback` also detects a raw Scattermind/Concept Map JSON object pasted into `idea`, `ideaText`, `optionsText`, or wrapper keys such as `payload`; it expands that object before ranking and reports `input.embeddedPayloadSource` so the public form can accept copy/paste exports without a custom import screen. Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. Handoff `readiness` now gives downstream bridge consumers a deterministic gate: `ready`, `usable-with-warnings`, `needs-source-context`, or `blocked`, with blockers and next checks for missing evidence, source trace, duplicate IDs, or active source-non-goal conflicts. For low-friction handoff, `/api/rank-feedback` also detects a raw Scattermind/Concept Map JSON object pasted into `idea`, `ideaText`, `optionsText`, or wrapper keys such as `payload`; it expands that object before ranking and reports `input.embeddedPayloadSource` so the public form can accept copy/paste exports without a custom import screen.
+4 -1
View File
@@ -68,7 +68,10 @@ function parsePastedJsonPayload(value) {
|| parsed.ideaText || parsed.idea_text || parsed.originalPrompt || parsed.original_prompt || parsed.sourceSummary || parsed.source_summary || parsed.opening_reflection || parsed.restated_idea || parsed.ideaText || parsed.idea_text || parsed.originalPrompt || parsed.original_prompt || parsed.sourceSummary || parsed.source_summary || parsed.opening_reflection || parsed.restated_idea
|| Array.isArray(parsed.features) || Array.isArray(parsed.actions) || Array.isArray(parsed.candidates) || Array.isArray(parsed.features) || Array.isArray(parsed.actions) || Array.isArray(parsed.candidates)
|| Array.isArray(parsed.nextActions) || Array.isArray(parsed.next_actions) || Array.isArray(parsed.nextMoves) || Array.isArray(parsed.next_moves) || Array.isArray(parsed.nextActions) || Array.isArray(parsed.next_actions) || Array.isArray(parsed.nextMoves) || Array.isArray(parsed.next_moves)
|| Array.isArray(parsed.validateNext) || Array.isArray(parsed.validate_next) || Array.isArray(parsed.deferred) || Array.isArray(parsed.parkingLot) || Array.isArray(parsed.parking_lot) || Array.isArray(parsed.doFirst) || Array.isArray(parsed.do_first) || Array.isArray(parsed.continueFirst) || Array.isArray(parsed.continue_first) || Array.isArray(parsed.makeTangible) || Array.isArray(parsed.make_tangible)
|| Array.isArray(parsed.validateNext) || Array.isArray(parsed.validate_next) || Array.isArray(parsed.evidenceNext) || Array.isArray(parsed.evidence_next) || Array.isArray(parsed.tryNext) || Array.isArray(parsed.try_next)
|| Array.isArray(parsed.deferred) || Array.isArray(parsed.holdForLater) || Array.isArray(parsed.hold_for_later)
|| Array.isArray(parsed.parkingLot) || Array.isArray(parsed.parking_lot) || Array.isArray(parsed.setAside) || Array.isArray(parsed.set_aside)
); );
return looksLikeBridgePayload ? parsed : null; return looksLikeBridgePayload ? parsed : null;
} catch { } catch {
+46 -1
View File
@@ -1127,7 +1127,52 @@ try {
assert.equal(directEnvelopeSections.handoff.readiness.status, 'ready'); assert.equal(directEnvelopeSections.handoff.readiness.status, 'ready');
assert.deepEqual(directEnvelopeSections.handoff.warnings, []); assert.deepEqual(directEnvelopeSections.handoff.warnings, []);
console.log(JSON.stringify({ ok: true, top: data.ranked[0].id, hintedTop: hinted.ranked[0].id, actionTop: actions.ranked[0].id, nestedConceptTop: nestedConcept.ranked[0].id, nonGoalTop: nonGoal.ranked[0].id, structuredContextTop: structuredContext.ranked[0].id, lensOnlyTop: lensOnly.ranked[0].id, scattermindPaidShapeTop: scattermindPaidShape.ranked[0].id, mergedContextTop: mergedContext.ranked[0].id, embeddedJsonTop: embeddedJson.ranked[0].id, fencedJsonTop: fencedJson.ranked[0].id, embeddedSnapshotTop: embeddedSnapshot.ranked[0].id, sourceExcerptTop: sourceExcerpt.ranked[0].id, snakeCaseBridgeTop: snakeCaseBridge.ranked[0].id, nextStepsAliasTop: nextStepsAlias.ranked[0].id, summaryGuardrailTop: summaryGuardrail.ranked[0].id, bridgeEnvelopeTop: bridgeEnvelope.ranked[0].id, directEnvelopeSectionsTop: directEnvelopeSections.ranked[0].id, duplicateIds: duplicateIds.ranked.map(item => item.id), readiness: data.handoff.readiness.status, provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2)); const softDirectLaneAliasesResponse = await fetch(`${base}/api/rank-feedback`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
schema: 'scattermind-ranker-bridge-v1',
rankReady: {
schema: 'prioritix-feature-set-v1',
sourceName: 'Scattermind',
reference_code: 'SM-SOFT-LANES-1',
working_name: 'Soft direct lane labels',
ideaText: 'Scattermind used human-friendly direct lane labels rather than a buildOrder wrapper.',
context: {
targetAudience: 'Tired non-AI-native maker',
constraints: ['Keep the bridge action-first'],
avoid: ['Avoid saved workspaces before manual proof'],
},
continueFirst: [
{ id: 'soft-continue-proof', move: 'Soft-labelled continuation proof', why: 'Accept Continue first as the active build-order lane without asking Scattermind for harsher copy.', evidenceNeeded: 'Can one soft-labelled export produce a Do first lane?', sourceItemId: 'soft-do-1', sourceSection: 'rankReady.continueFirst', rankerHints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } },
],
evidenceNext: [
{ id: 'soft-evidence-copy', move: 'Soft-labelled evidence brief', why: 'Preserve Evidence next as Validate next.', evidenceNeeded: 'Does the handoff keep the softer source label traceable?', sourceItemId: 'soft-test-1', sourceSection: 'rankReady.evidenceNext' },
],
holdForLater: [
{ id: 'soft-later-export', move: 'Soft-labelled later export polish', why: 'Nice after proof, but not first.', evidenceNeeded: 'Does polish matter after the first proof?', sourceItemId: 'soft-defer-1', sourceSection: 'rankReady.holdForLater' },
],
setAside: [
{ id: 'soft-saved-workspace', move: 'Soft-labelled saved workspace dashboard', why: 'Accounts and saved projects before proof.', evidenceNeeded: 'No proof yet.', sourceItemId: 'soft-park-1', sourceSection: 'rankReady.setAside' },
],
},
}),
});
assert.equal(softDirectLaneAliasesResponse.status, 200);
const softDirectLaneAliases = await softDirectLaneAliasesResponse.json();
assert.equal(softDirectLaneAliases.input.provenance.artifactId, 'SM-SOFT-LANES-1');
assert.equal(softDirectLaneAliases.input.optionCount, 4);
assert.equal(softDirectLaneAliases.ranked[0].id, 'soft-continue-proof');
assert.equal(softDirectLaneAliases.ranked[0].lane.id, 'do');
assert.equal(softDirectLaneAliases.ranked.find(item => item.id === 'soft-evidence-copy').lane.id, 'test');
assert.equal(softDirectLaneAliases.ranked.find(item => item.id === 'soft-evidence-copy').lane.source, 'hint');
assert.equal(softDirectLaneAliases.ranked.find(item => item.id === 'soft-later-export').lane.id, 'defer');
assert.equal(softDirectLaneAliases.ranked.find(item => item.id === 'soft-saved-workspace').lane.id, 'park');
assert.equal(softDirectLaneAliases.handoff.itemTrace.find(item => item.id === 'soft-saved-workspace').sourceSection, 'rankReady.setAside');
assert.equal(softDirectLaneAliases.handoff.readiness.status, 'ready');
assert.deepEqual(softDirectLaneAliases.handoff.warnings, []);
console.log(JSON.stringify({ ok: true, top: data.ranked[0].id, hintedTop: hinted.ranked[0].id, actionTop: actions.ranked[0].id, nestedConceptTop: nestedConcept.ranked[0].id, nonGoalTop: nonGoal.ranked[0].id, structuredContextTop: structuredContext.ranked[0].id, lensOnlyTop: lensOnly.ranked[0].id, scattermindPaidShapeTop: scattermindPaidShape.ranked[0].id, mergedContextTop: mergedContext.ranked[0].id, embeddedJsonTop: embeddedJson.ranked[0].id, fencedJsonTop: fencedJson.ranked[0].id, embeddedSnapshotTop: embeddedSnapshot.ranked[0].id, sourceExcerptTop: sourceExcerpt.ranked[0].id, snakeCaseBridgeTop: snakeCaseBridge.ranked[0].id, nextStepsAliasTop: nextStepsAlias.ranked[0].id, summaryGuardrailTop: summaryGuardrail.ranked[0].id, bridgeEnvelopeTop: bridgeEnvelope.ranked[0].id, directEnvelopeSectionsTop: directEnvelopeSections.ranked[0].id, softDirectLaneAliasesTop: softDirectLaneAliases.ranked[0].id, duplicateIds: duplicateIds.ranked.map(item => item.id), readiness: data.handoff.readiness.status, provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2));
} finally { } finally {
server.kill('SIGTERM'); server.kill('SIGTERM');
} }
+34 -12
View File
@@ -565,11 +565,25 @@ function looksLikeRankPayload(value = {}) {
|| Array.isArray(value.nextMoves) || Array.isArray(value.nextMoves)
|| Array.isArray(value.next_moves) || Array.isArray(value.next_moves)
|| Array.isArray(value.candidates) || Array.isArray(value.candidates)
|| Array.isArray(value.doFirst)
|| Array.isArray(value.do_first)
|| Array.isArray(value.continueFirst)
|| Array.isArray(value.continue_first)
|| Array.isArray(value.makeTangible)
|| Array.isArray(value.make_tangible)
|| Array.isArray(value.validateNext) || Array.isArray(value.validateNext)
|| Array.isArray(value.validate_next) || Array.isArray(value.validate_next)
|| Array.isArray(value.evidenceNext)
|| Array.isArray(value.evidence_next)
|| Array.isArray(value.tryNext)
|| Array.isArray(value.try_next)
|| Array.isArray(value.deferred) || Array.isArray(value.deferred)
|| Array.isArray(value.holdForLater)
|| Array.isArray(value.hold_for_later)
|| Array.isArray(value.parkingLot) || Array.isArray(value.parkingLot)
|| Array.isArray(value.parking_lot) || Array.isArray(value.parking_lot)
|| Array.isArray(value.setAside)
|| Array.isArray(value.set_aside)
); );
} }
@@ -943,6 +957,9 @@ function optionsFromBody(body = {}) {
{ items: body.candidates, sourceSection: 'candidates' }, { items: body.candidates, sourceSection: 'candidates' },
{ items: envelope.candidates, sourceSection: 'ranker-input.candidates' }, { items: envelope.candidates, sourceSection: 'ranker-input.candidates' },
{ items: featureSet.candidates, sourceSection: 'feature-set.candidates' }, { items: featureSet.candidates, sourceSection: 'feature-set.candidates' },
{ items: body.doFirst || body.do_first || body.buildFirst || body.build_first || body.buildNow || body.build_now || body.continueFirst || body.continue_first || body.makeTangible || body.make_tangible || body.startHere || body.start_here, sourceSection: 'doFirst', defaultLane: 'do-first' },
{ items: envelope.doFirst || envelope.do_first || envelope.buildFirst || envelope.build_first || envelope.buildNow || envelope.build_now || envelope.continueFirst || envelope.continue_first || envelope.makeTangible || envelope.make_tangible || envelope.startHere || envelope.start_here, sourceSection: 'ranker-input.doFirst', defaultLane: 'do-first' },
{ items: featureSet.doFirst || featureSet.do_first || featureSet.buildFirst || featureSet.build_first || featureSet.buildNow || featureSet.build_now || featureSet.continueFirst || featureSet.continue_first || featureSet.makeTangible || featureSet.make_tangible || featureSet.startHere || featureSet.start_here, sourceSection: 'feature-set.doFirst', defaultLane: 'do-first' },
{ items: body.experiments, sourceSection: 'experiments', defaultLane: 'validate-next' }, { items: body.experiments, sourceSection: 'experiments', defaultLane: 'validate-next' },
{ items: body.validationTests || body.validation_tests, sourceSection: 'experiments', defaultLane: 'validate-next' }, { items: body.validationTests || body.validation_tests, sourceSection: 'experiments', defaultLane: 'validate-next' },
{ items: body.proofTests || body.proof_tests, sourceSection: 'experiments', defaultLane: 'validate-next' }, { items: body.proofTests || body.proof_tests, sourceSection: 'experiments', defaultLane: 'validate-next' },
@@ -952,24 +969,28 @@ function optionsFromBody(body = {}) {
{ items: featureSet.experiments, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' }, { items: featureSet.experiments, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.validationTests || featureSet.validation_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' }, { items: featureSet.validationTests || featureSet.validation_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: featureSet.proofTests || featureSet.proof_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' }, { items: featureSet.proofTests || featureSet.proof_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' },
{ items: envelope.validateNext || envelope.validate_next || envelope.validate || envelope.validation, sourceSection: 'ranker-input.validateNext', defaultLane: 'validate-next' }, { items: body.validateNext || body.validate_next || body.validate || body.validation || body.evidenceNext || body.evidence_next || body.tryNext || body.try_next || body.learnNext || body.learn_next || body.testManually || body.test_manually, sourceSection: 'validateNext', defaultLane: 'validate-next' },
{ items: envelope.deferred || envelope.defer || envelope.later, sourceSection: 'ranker-input.deferred', defaultLane: 'defer' }, { items: body.deferred || body.defer || body.later || body.afterProof || body.after_proof || body.holdForLater || body.hold_for_later || body.notYet || body.not_yet, sourceSection: 'deferred', defaultLane: 'defer' },
{ items: envelope.parkingLot || envelope.parking_lot || envelope.park || envelope.parked, sourceSection: 'ranker-input.parkingLot', defaultLane: 'park' }, { items: body.parkingLot || body.parking_lot || body.park || body.parked || body.probablyNoise || body.probably_noise || body.setAside || body.set_aside || body.outOfScope || body.out_of_scope, sourceSection: 'parkingLot', defaultLane: 'park' },
{ items: featureSet.validateNext || featureSet.validate_next || featureSet.validate || featureSet.validation, sourceSection: 'feature-set.validateNext', defaultLane: 'validate-next' }, { items: envelope.validateNext || envelope.validate_next || envelope.validate || envelope.validation || envelope.evidenceNext || envelope.evidence_next || envelope.tryNext || envelope.try_next || envelope.learnNext || envelope.learn_next || envelope.testManually || envelope.test_manually, sourceSection: 'ranker-input.validateNext', defaultLane: 'validate-next' },
{ items: featureSet.deferred || featureSet.defer || featureSet.later, sourceSection: 'feature-set.deferred', defaultLane: 'defer' }, { items: envelope.deferred || envelope.defer || envelope.later || envelope.afterProof || envelope.after_proof || envelope.holdForLater || envelope.hold_for_later || envelope.notYet || envelope.not_yet, sourceSection: 'ranker-input.deferred', defaultLane: 'defer' },
{ items: featureSet.parkingLot || featureSet.parking_lot || featureSet.park || featureSet.parked, sourceSection: 'feature-set.parkingLot', defaultLane: 'park' }, { items: envelope.parkingLot || envelope.parking_lot || envelope.park || envelope.parked || envelope.probablyNoise || envelope.probably_noise || envelope.setAside || envelope.set_aside || envelope.outOfScope || envelope.out_of_scope, sourceSection: 'ranker-input.parkingLot', defaultLane: 'park' },
{ items: featureSet.validateNext || featureSet.validate_next || featureSet.validate || featureSet.validation || featureSet.evidenceNext || featureSet.evidence_next || featureSet.tryNext || featureSet.try_next || featureSet.learnNext || featureSet.learn_next || featureSet.testManually || featureSet.test_manually, sourceSection: 'feature-set.validateNext', defaultLane: 'validate-next' },
{ items: featureSet.deferred || featureSet.defer || featureSet.later || featureSet.afterProof || featureSet.after_proof || featureSet.holdForLater || featureSet.hold_for_later || featureSet.notYet || featureSet.not_yet, sourceSection: 'feature-set.deferred', defaultLane: 'defer' },
{ items: featureSet.parkingLot || featureSet.parking_lot || featureSet.park || featureSet.parked || featureSet.probablyNoise || featureSet.probably_noise || featureSet.setAside || featureSet.set_aside || featureSet.outOfScope || featureSet.out_of_scope, sourceSection: 'feature-set.parkingLot', defaultLane: 'park' },
]); ]);
const conceptMapCandidateGroup = compactCandidateGroup([ const conceptMapCandidateGroup = compactCandidateGroup([
{ items: conceptMap.nextActions || conceptMap.next_actions || conceptMap.nextSteps || conceptMap.next_steps || conceptMap.recommendedNextSteps || conceptMap.recommended_next_steps, sourceSection: 'concept-map.nextActions' }, { items: conceptMap.nextActions || conceptMap.next_actions || conceptMap.nextSteps || conceptMap.next_steps || conceptMap.recommendedNextSteps || conceptMap.recommended_next_steps, sourceSection: 'concept-map.nextActions' },
{ items: conceptMap.nextMoves || conceptMap.next_moves, sourceSection: 'concept-map.nextMoves' }, { items: conceptMap.nextMoves || conceptMap.next_moves, sourceSection: 'concept-map.nextMoves' },
{ items: conceptMap.features, sourceSection: 'concept-map.features' }, { items: conceptMap.features, sourceSection: 'concept-map.features' },
{ items: conceptMap.candidates, sourceSection: 'concept-map.candidates' }, { items: conceptMap.candidates, sourceSection: 'concept-map.candidates' },
{ items: conceptMap.validateNext || conceptMap.validate_next || conceptMap.validate || conceptMap.validation, sourceSection: 'concept-map.validateNext', defaultLane: 'validate-next' }, { items: conceptMap.doFirst || conceptMap.do_first || conceptMap.buildFirst || conceptMap.build_first || conceptMap.buildNow || conceptMap.build_now || conceptMap.continueFirst || conceptMap.continue_first || conceptMap.makeTangible || conceptMap.make_tangible || conceptMap.startHere || conceptMap.start_here, sourceSection: 'concept-map.doFirst', defaultLane: 'do-first' },
{ items: conceptMap.validateNext || conceptMap.validate_next || conceptMap.validate || conceptMap.validation || conceptMap.evidenceNext || conceptMap.evidence_next || conceptMap.tryNext || conceptMap.try_next || conceptMap.learnNext || conceptMap.learn_next || conceptMap.testManually || conceptMap.test_manually, sourceSection: 'concept-map.validateNext', defaultLane: 'validate-next' },
{ items: conceptMap.experiments, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' }, { items: conceptMap.experiments, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.validationTests || conceptMap.validation_tests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' }, { items: conceptMap.validationTests || conceptMap.validation_tests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.proofTests || conceptMap.proof_tests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' }, { items: conceptMap.proofTests || conceptMap.proof_tests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' },
{ items: conceptMap.deferred || conceptMap.defer || conceptMap.later, sourceSection: 'concept-map.deferred', defaultLane: 'defer' }, { items: conceptMap.deferred || conceptMap.defer || conceptMap.later || conceptMap.afterProof || conceptMap.after_proof || conceptMap.holdForLater || conceptMap.hold_for_later || conceptMap.notYet || conceptMap.not_yet, sourceSection: 'concept-map.deferred', defaultLane: 'defer' },
{ items: conceptMap.parkingLot || conceptMap.parking_lot || conceptMap.park || conceptMap.parked, sourceSection: 'concept-map.parkingLot', defaultLane: 'park' }, { items: conceptMap.parkingLot || conceptMap.parking_lot || conceptMap.park || conceptMap.parked || conceptMap.probablyNoise || conceptMap.probably_noise || conceptMap.setAside || conceptMap.set_aside || conceptMap.outOfScope || conceptMap.out_of_scope, sourceSection: 'concept-map.parkingLot', defaultLane: 'park' },
]); ]);
const snapshotCandidateGroup = compactCandidateGroup([ const snapshotCandidateGroup = compactCandidateGroup([
{ items: snapshot.nextActions || snapshot.next_actions || snapshot.nextSteps || snapshot.next_steps || snapshot.recommendedNextSteps || snapshot.recommended_next_steps, sourceSection: 'snapshot.nextActions' }, { items: snapshot.nextActions || snapshot.next_actions || snapshot.nextSteps || snapshot.next_steps || snapshot.recommendedNextSteps || snapshot.recommended_next_steps, sourceSection: 'snapshot.nextActions' },
@@ -977,12 +998,13 @@ function optionsFromBody(body = {}) {
{ items: snapshot.actions, sourceSection: 'snapshot.actions' }, { items: snapshot.actions, sourceSection: 'snapshot.actions' },
{ items: snapshot.features, sourceSection: 'snapshot.features' }, { items: snapshot.features, sourceSection: 'snapshot.features' },
{ items: snapshot.candidates, sourceSection: 'snapshot.candidates' }, { items: snapshot.candidates, sourceSection: 'snapshot.candidates' },
{ items: snapshot.validateNext || snapshot.validate_next || snapshot.validate || snapshot.validation, sourceSection: 'snapshot.validateNext', defaultLane: 'validate-next' }, { items: snapshot.doFirst || snapshot.do_first || snapshot.buildFirst || snapshot.build_first || snapshot.buildNow || snapshot.build_now || snapshot.continueFirst || snapshot.continue_first || snapshot.makeTangible || snapshot.make_tangible || snapshot.startHere || snapshot.start_here, sourceSection: 'snapshot.doFirst', defaultLane: 'do-first' },
{ items: snapshot.validateNext || snapshot.validate_next || snapshot.validate || snapshot.validation || snapshot.evidenceNext || snapshot.evidence_next || snapshot.tryNext || snapshot.try_next || snapshot.learnNext || snapshot.learn_next || snapshot.testManually || snapshot.test_manually, sourceSection: 'snapshot.validateNext', defaultLane: 'validate-next' },
{ items: snapshot.experiments, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' }, { items: snapshot.experiments, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.validationTests || snapshot.validation_tests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' }, { items: snapshot.validationTests || snapshot.validation_tests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.proofTests || snapshot.proof_tests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' }, { items: snapshot.proofTests || snapshot.proof_tests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' },
{ items: snapshot.deferred || snapshot.defer || snapshot.later, sourceSection: 'snapshot.deferred', defaultLane: 'defer' }, { items: snapshot.deferred || snapshot.defer || snapshot.later || snapshot.afterProof || snapshot.after_proof || snapshot.holdForLater || snapshot.hold_for_later || snapshot.notYet || snapshot.not_yet, sourceSection: 'snapshot.deferred', defaultLane: 'defer' },
{ items: snapshot.parkingLot || snapshot.parking_lot || snapshot.park || snapshot.parked, sourceSection: 'snapshot.parkingLot', defaultLane: 'park' }, { items: snapshot.parkingLot || snapshot.parking_lot || snapshot.park || snapshot.parked || snapshot.probablyNoise || snapshot.probably_noise || snapshot.setAside || snapshot.set_aside || snapshot.outOfScope || snapshot.out_of_scope, sourceSection: 'snapshot.parkingLot', defaultLane: 'park' },
]); ]);
const groupedCandidates = [ const groupedCandidates = [
...directCandidateGroup, ...directCandidateGroup,