From 0447e89ca6b6b6bb94e6ba58560d3bd744b6c46e Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Wed, 27 May 2026 20:13:21 +0200 Subject: [PATCH] Accept Scattermind first-48-hour handoffs --- README.md | 2 +- scripts/check-rank-feedback.mjs | 35 +++++++++++++++++++++++++++++++++ server.js | 31 +++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bc451f9..913fdd6 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Candidate items may include optional 1–10 `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. Ranker also accepts the current Scattermind storage-row shape with `referenceCode`, `ideaText`, `context`, and string-valued `fullReadingJson` / `full_reading_json`; it expands the saved paid Concept Map before ranking so operators do not have to hand-copy lenses out of Appwrite rows. It also accepts private reading/API envelopes with object-valued `reading` / `fullReading` and `glimpse`, preserving `referenceCode` and `initialPrompt` while treating `reading` as the paid Concept Map. Stringified bridge envelopes in fields such as `rankerInput`, `rankerBridge`, `rankReady`, `bridgePayload`, or `continuationPlan` are expanded the same way, so Appwrite/string-copy handoffs do not have to be manually unwrapped before ranking. If the row only has free Snapshot data (`glimpseJson` / `glimpse_json` / `snapshotJson`), Ranker expands that Snapshot into a minimal continuation order: one manual proof plus the first evidence question, with the Snapshot reference code/title preserved for provenance. The public paste form mirrors this: a prose-wrapped/fenced Appwrite row paste stays intact for the API instead of unwrapping the stringified reading into nonsense fields. 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 into the bridge handoff. -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`, with either colon or reader-friendly dash separators (`Continue first: …`, `Continue first — …`, `Evidence next - …`). 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. Ranker also accepts softer continuation envelopes named `rankerBridge`, `continuation`, or `continuationPlan`, candidate arrays named `possibleNextMoves`, `suggestedNextMoves`, `recommendations`, or `opportunities`, laned `buildOrderPreview` / `build_order_preview` objects, and evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`). Direct candidate objects may use reader-friendly prose keys like `text`, `content`, `summary`, `why_it_matters`, `evidence_to_collect`, `first_proof_step`, `green_flag`, and `red_flag`; Ranker normalizes those into title, value, evidence, next-step, success, and kill-signal fields so Scattermind does not have to rename paid Concept Map language into software-feature jargon. If a paid Concept Map has no labelled Build Order/action threads but does include `closing_note` / `closingNote` plus decision questions, Ranker treats the closing note as the active 48-hour Do first move and keeps the questions in Validate next. This lets Scattermind pass reader-friendly Concept Map copy without renaming everything into software-feature language. +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`, with either colon or reader-friendly dash separators (`Continue first: …`, `Continue first — …`, `Evidence next - …`). 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. Ranker also accepts softer continuation envelopes named `rankerBridge`, `continuation`, or `continuationPlan`, candidate arrays named `possibleNextMoves`, `suggestedNextMoves`, `recommendations`, or `opportunities`, laned `buildOrderPreview` / `build_order_preview` objects, first-48-hour action arrays (`next48Hours`, `next_48_hours`, `first48Hours`, `first_48_hours`, `nextTwoDays`, `next_two_days`), and evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `proofQuestions` / `proof_questions`, `validationQuestions` / `validation_questions`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`). Direct candidate objects may use reader-friendly prose keys like `text`, `content`, `summary`, `step`, `task`, `instruction`, `why_it_matters`, `evidence_to_collect`, `first_proof_step`, `green_flag`, and `red_flag`; Ranker normalizes those into title, value, evidence, next-step, success, and kill-signal fields so Scattermind does not have to rename paid Concept Map language into software-feature jargon. If a paid Concept Map has no labelled Build Order/action threads but does include `closing_note` / `closingNote` plus decision questions, Ranker treats the closing note as the active 48-hour Do first move and keeps the questions in Validate next. This lets Scattermind pass reader-friendly Concept Map copy without renaming everything into software-feature language. 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; soft guardrail language such as “this is not a dashboard” or “keep auth/billing/workspaces out until proof” is promoted into non-goals, not merely background context. Ranker now also infers a light `ideaRoute` from the Scattermind source text and carries it in `input.decisionContext` / `handoff.decisionContext`; for game concepts it automatically adds anti-SaaS non-goals so account/dashboard/workspace/subscription candidates cannot win a playable-prototype build order just because they were phrased loudly. 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. Handoff `activeSlice` (`ranker-active-slice-v1`) is the compact machine-readable continuation unit: one active item, its proof/evidence/success/kill signals, source anchor, held-back items, readiness status, and the rule that only this slice is build-ready. For tired first-screen users, `brief.decisionReceipt` repeats the one active move, first proof step, evidence question, held-back items, source anchor, and the handoff rule that only Do first is active; use it as the compact result strip before showing the full lane board. 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. Exact free Snapshot JSON (`working_name`, `restated_idea`, `lenses.shape`, `questions_to_sit_with`, `reference_code`) is rankable too: Ranker derives a manual proof active slice plus evidence questions, carrying the Snapshot reference code/title into provenance so a Snapshot-only handoff does not need a paid Concept Map before it can produce a useful build order. If a Concept Map only carries `questions_to_sit_with` / `questionsToSitWith` / `openQuestions` and no explicit build-order lanes or action threads, Ranker converts those questions into Validate-next evidence actions with source trace instead of pretending they are software features. diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs index 0207677..16ba20b 100644 --- a/scripts/check-rank-feedback.mjs +++ b/scripts/check-rank-feedback.mjs @@ -374,6 +374,41 @@ try { assert.match(singleThreadQuestion.brief.decisionReceipt.firstProofStep, /manual proof/i); assert.equal(singleThreadQuestion.handoff.readiness.status, 'ready'); + const first48HoursResponse = await fetch(`${base}/api/rank-feedback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sourceName: 'Scattermind', + artifactId: 'concept_map_first_48_hours', + originalPrompt: 'Clarify a tiny offer idea and produce the first 48 hours of continuation work.', + context: 'Solo builder. Manual proof first. Avoid saved workspace dashboards before evidence.', + conceptMap: { + working_name: 'First 48 hour handoff', + first_48_hours: [ + { + id: 'send-manual-preview', + step: 'Send one manual build-order preview to a real freelancer', + why_it_matters: 'This proves whether the continuation helps before UI work.', + proof_question: 'Can they name the first move without another explanation?', + }, + { + id: 'workspace-dashboard', + task: 'Create a saved workspace dashboard for every Concept Map', + evidence_to_collect: 'Would users want saved boards later?', + }, + ], + }, + }), + }); + assert.equal(first48HoursResponse.status, 200); + const first48Hours = await first48HoursResponse.json(); + assert.equal(first48Hours.input.optionCount, 2, 'first_48_hours arrays should become rankable continuation candidates'); + assert.equal(first48Hours.buildOrder.doFirst[0], 'send-manual-preview'); + assert.equal(first48Hours.ranked.find(item => item.id === 'send-manual-preview').provenance.sourceSection, 'concept-map.next48Hours'); + assert.match(first48Hours.handoff.activeSlice.proof.evidenceQuestion, /first move/); + assert.equal(first48Hours.ranked.find(item => item.id === 'workspace-dashboard').lane.source, 'source-non-goal'); + assert.equal(first48Hours.handoff.readiness.status, 'ready'); + const softDashLabelResponse = await fetch(`${base}/api/rank-feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, diff --git a/server.js b/server.js index 411c18e..7f4a9a5 100644 --- a/server.js +++ b/server.js @@ -599,6 +599,12 @@ function looksLikeRankPayload(value = {}) { || Array.isArray(value.suggested_actions) || Array.isArray(value.nextActions) || Array.isArray(value.next_actions) + || Array.isArray(value.next48Hours) + || Array.isArray(value.next_48_hours) + || Array.isArray(value.first48Hours) + || Array.isArray(value.first_48_hours) + || Array.isArray(value.nextTwoDays) + || Array.isArray(value.next_two_days) || Array.isArray(value.nextMoves) || Array.isArray(value.next_moves) || Array.isArray(value.possibleNextMoves) @@ -641,6 +647,10 @@ function looksLikeRankPayload(value = {}) { || Array.isArray(value.questionsToSitWith) || Array.isArray(value.evidenceQuestions) || Array.isArray(value.evidence_questions) + || Array.isArray(value.proofQuestions) + || Array.isArray(value.proof_questions) + || Array.isArray(value.validationQuestions) + || Array.isArray(value.validation_questions) || Array.isArray(value.decisionQuestions) || Array.isArray(value.decision_questions) || Array.isArray(value.questionsToAnswer) @@ -1046,13 +1056,13 @@ function nonGoalConflicts(optionText, decisionContext = {}) { function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSourceSection = '', defaultRecommendedLane = '') { const rawValue = typeof item === 'string' || typeof item === 'number' ? String(item) : ''; const raw = rawValue ? { action: rawValue } : objectFrom(item); - const title = cleanText(raw.title || raw.name || raw.action || raw.move || raw.nextMove || raw.next_move || raw.nextStep || raw.next_step || raw.recommendedNextStep || raw.recommended_next_step || raw.experiment || raw.testName || raw.test_name || raw.hypothesis || raw.label || raw.text || raw.content || raw.summary || '', 140); + const title = cleanText(raw.title || raw.name || raw.action || raw.move || raw.step || raw.task || raw.instruction || raw.nextMove || raw.next_move || raw.nextStep || raw.next_step || raw.recommendedNextStep || raw.recommended_next_step || raw.experiment || raw.testName || raw.test_name || raw.hypothesis || raw.label || raw.text || raw.content || raw.summary || '', 140); const proofSteps = cleanTextList(raw.proofSteps || raw.proof_steps || raw.proof || raw.firstProof || raw.first_proof || raw.validationSteps || raw.validation_steps || raw.steps || raw.method, 5, 180); const dependencies = cleanTextList(raw.dependencies || raw.blockedBy || raw.blocked_by, 5, 120); const evidenceNeeded = cleanText(raw.evidenceNeeded || raw.evidence_needed || raw.evidenceToCollect || raw.evidence_to_collect || raw.evidence || raw.test || raw.proofQuestion || raw.proof_question || raw.evidenceQuestion || raw.evidence_question || raw.questionToAnswer || raw.question_to_answer || raw.question || raw.learningGoal || raw.learning_goal || '', 260); const userValue = cleanText(raw.userValue || raw.user_value || raw.value || raw.outcome || raw.why || raw.whyItMatters || raw.why_it_matters || raw.whyNow || raw.why_now, 260); const risk = cleanText(raw.risk || raw.assumption || raw.unknown || raw.watchOut || raw.watch_out || '', 220); - const nextStep = cleanText(raw.nextStep || raw.next_step || raw.nextAction || raw.next_action || raw.firstStep || raw.first_step || raw.manualStep || raw.manual_step || raw.actionToTake || raw.action_to_take || raw.firstProofStep || raw.first_proof_step || '', 260); + const nextStep = cleanText(raw.nextStep || raw.next_step || raw.nextAction || raw.next_action || raw.firstStep || raw.first_step || raw.manualStep || raw.manual_step || raw.actionToTake || raw.action_to_take || raw.firstProofStep || raw.first_proof_step || raw.step || raw.task || raw.instruction || '', 260); const successSignal = cleanText(raw.successSignal || raw.success_signal || raw.successCriteria || raw.success_criteria || raw.successMetric || raw.success_metric || raw.greenLight || raw.green_light || raw.greenFlag || raw.green_flag || raw.signalToSee || raw.signal_to_see || '', 260); const killSignal = cleanText(raw.killSignal || raw.kill_signal || raw.stopSignal || raw.stop_signal || raw.redFlag || raw.red_flag || raw.failureSignal || raw.failure_signal || raw.cutIf || raw.cut_if || raw.stopIf || raw.stop_if || '', 260); const rawLane = cleanText(raw.lane || '', 40); @@ -1328,7 +1338,7 @@ function optionsFromSnapshotReading(source = {}, sourceSection = 'snapshot') { const shapeText = cleanMultiline(lensContent(rawShapeLens), 700); const workingName = cleanText(reading.working_name || reading.workingName || reading.snapshotTitle || reading.snapshot_title || reading.title || reading.name || '', 90); const restatedIdea = cleanText(reading.restated_idea || reading.restatedIdea || reading.ideaText || reading.idea_text || reading.idea || '', 260); - const questions = cleanFlexibleTextList(reading.questions_to_sit_with || reading.questionsToSitWith || reading.evidenceQuestions || reading.evidence_questions || reading.decisionQuestions || reading.decision_questions || reading.questionsToAnswer || reading.questions_to_answer || reading.followupQuestions || reading.followup_questions || reading.openQuestions || reading.open_questions, 4, 240); + const questions = cleanFlexibleTextList(reading.questions_to_sit_with || reading.questionsToSitWith || reading.evidenceQuestions || reading.evidence_questions || reading.proofQuestions || reading.proof_questions || reading.validationQuestions || reading.validation_questions || reading.decisionQuestions || reading.decision_questions || reading.questionsToAnswer || reading.questions_to_answer || reading.followupQuestions || reading.followup_questions || reading.openQuestions || reading.open_questions, 4, 240); const hasSnapshotShape = Boolean(workingName || restatedIdea || shapeText || questions.length); if (!hasSnapshotShape || (!shapeText && !restatedIdea && questions.length < 1)) return []; @@ -1383,6 +1393,9 @@ function optionsFromBody(body = {}) { { items: body.nextActions || body.next_actions || body.nextSteps || body.next_steps || body.recommendedNextSteps || body.recommended_next_steps || body.recommendedActions || body.recommended_actions || body.suggestedActions || body.suggested_actions, sourceSection: 'nextActions' }, { items: envelope.nextActions || envelope.next_actions || envelope.nextSteps || envelope.next_steps || envelope.recommendedNextSteps || envelope.recommended_next_steps || envelope.recommendedActions || envelope.recommended_actions || envelope.suggestedActions || envelope.suggested_actions, sourceSection: 'ranker-input.nextActions' }, { items: featureSet.nextActions || featureSet.next_actions || featureSet.nextSteps || featureSet.next_steps || featureSet.recommendedNextSteps || featureSet.recommended_next_steps || featureSet.recommendedActions || featureSet.recommended_actions || featureSet.suggestedActions || featureSet.suggested_actions, sourceSection: 'feature-set.nextActions' }, + { items: body.next48Hours || body.next_48_hours || body.first48Hours || body.first_48_hours || body.nextTwoDays || body.next_two_days, sourceSection: 'next48Hours', defaultLane: 'do-first' }, + { items: envelope.next48Hours || envelope.next_48_hours || envelope.first48Hours || envelope.first_48_hours || envelope.nextTwoDays || envelope.next_two_days, sourceSection: 'ranker-input.next48Hours', defaultLane: 'do-first' }, + { items: featureSet.next48Hours || featureSet.next_48_hours || featureSet.first48Hours || featureSet.first_48_hours || featureSet.nextTwoDays || featureSet.next_two_days, sourceSection: 'feature-set.next48Hours', defaultLane: 'do-first' }, { items: body.nextMoves || body.next_moves, sourceSection: 'nextMoves' }, { items: envelope.nextMoves || envelope.next_moves, sourceSection: 'ranker-input.nextMoves' }, { items: featureSet.nextMoves || featureSet.next_moves, sourceSection: 'feature-set.nextMoves' }, @@ -1419,6 +1432,7 @@ function optionsFromBody(body = {}) { ]); const conceptMapCandidateGroup = compactCandidateGroup([ { items: conceptMap.nextActions || conceptMap.next_actions || conceptMap.nextSteps || conceptMap.next_steps || conceptMap.recommendedNextSteps || conceptMap.recommended_next_steps || conceptMap.recommendedActions || conceptMap.recommended_actions || conceptMap.suggestedActions || conceptMap.suggested_actions, sourceSection: 'concept-map.nextActions' }, + { items: conceptMap.next48Hours || conceptMap.next_48_hours || conceptMap.first48Hours || conceptMap.first_48_hours || conceptMap.nextTwoDays || conceptMap.next_two_days, sourceSection: 'concept-map.next48Hours', defaultLane: 'do-first' }, { items: conceptMap.nextMoves || conceptMap.next_moves, sourceSection: 'concept-map.nextMoves' }, { items: conceptMap.possibleNextMoves || conceptMap.possible_next_moves || conceptMap.suggestedNextMoves || conceptMap.suggested_next_moves || conceptMap.recommendations || conceptMap.opportunities, sourceSection: 'concept-map.possibleNextMoves' }, { items: conceptMap.features, sourceSection: 'concept-map.features' }, @@ -1434,6 +1448,7 @@ function optionsFromBody(body = {}) { ]); const snapshotCandidateGroup = compactCandidateGroup([ { items: snapshot.nextActions || snapshot.next_actions || snapshot.nextSteps || snapshot.next_steps || snapshot.recommendedNextSteps || snapshot.recommended_next_steps || snapshot.recommendedActions || snapshot.recommended_actions || snapshot.suggestedActions || snapshot.suggested_actions, sourceSection: 'snapshot.nextActions' }, + { items: snapshot.next48Hours || snapshot.next_48_hours || snapshot.first48Hours || snapshot.first_48_hours || snapshot.nextTwoDays || snapshot.next_two_days, sourceSection: 'snapshot.next48Hours', defaultLane: 'do-first' }, { items: snapshot.nextMoves || snapshot.next_moves, sourceSection: 'snapshot.nextMoves' }, { items: snapshot.possibleNextMoves || snapshot.possible_next_moves || snapshot.suggestedNextMoves || snapshot.suggested_next_moves || snapshot.recommendations || snapshot.opportunities, sourceSection: 'snapshot.possibleNextMoves' }, { items: snapshot.actions, sourceSection: 'snapshot.actions' }, @@ -1506,11 +1521,11 @@ function optionsFromBody(body = {}) { 'Thread to hold' ); const questionSource = firstArraySource([ - { items: conceptMap.questions_to_sit_with || conceptMap.questionsToSitWith || conceptMap.evidenceQuestions || conceptMap.evidence_questions || conceptMap.decisionQuestions || conceptMap.decision_questions || conceptMap.questionsToAnswer || conceptMap.questions_to_answer || conceptMap.followupQuestions || conceptMap.followup_questions || conceptMap.openQuestions || conceptMap.open_questions, sourceSection: 'concept-map.questionsToSitWith' }, - { items: snapshot.questions_to_sit_with || snapshot.questionsToSitWith || snapshot.evidenceQuestions || snapshot.evidence_questions || snapshot.decisionQuestions || snapshot.decision_questions || snapshot.questionsToAnswer || snapshot.questions_to_answer || snapshot.followupQuestions || snapshot.followup_questions || snapshot.openQuestions || snapshot.open_questions, sourceSection: 'snapshot.questionsToSitWith' }, - { items: envelope.questions_to_sit_with || envelope.questionsToSitWith || envelope.evidenceQuestions || envelope.evidence_questions || envelope.decisionQuestions || envelope.decision_questions || envelope.questionsToAnswer || envelope.questions_to_answer || envelope.followupQuestions || envelope.followup_questions || envelope.openQuestions || envelope.open_questions, sourceSection: 'ranker-input.questionsToSitWith' }, - { items: featureSet.questions_to_sit_with || featureSet.questionsToSitWith || featureSet.evidenceQuestions || featureSet.evidence_questions || featureSet.decisionQuestions || featureSet.decision_questions || featureSet.questionsToAnswer || featureSet.questions_to_answer || featureSet.followupQuestions || featureSet.followup_questions || featureSet.openQuestions || featureSet.open_questions, sourceSection: 'feature-set.questionsToSitWith' }, - { items: body.questions_to_sit_with || body.questionsToSitWith || body.evidenceQuestions || body.evidence_questions || body.decisionQuestions || body.decision_questions || body.questionsToAnswer || body.questions_to_answer || body.followupQuestions || body.followup_questions || body.openQuestions || body.open_questions, sourceSection: 'questionsToSitWith' }, + { items: conceptMap.questions_to_sit_with || conceptMap.questionsToSitWith || conceptMap.evidenceQuestions || conceptMap.evidence_questions || conceptMap.proofQuestions || conceptMap.proof_questions || conceptMap.validationQuestions || conceptMap.validation_questions || conceptMap.decisionQuestions || conceptMap.decision_questions || conceptMap.questionsToAnswer || conceptMap.questions_to_answer || conceptMap.followupQuestions || conceptMap.followup_questions || conceptMap.openQuestions || conceptMap.open_questions, sourceSection: 'concept-map.questionsToSitWith' }, + { items: snapshot.questions_to_sit_with || snapshot.questionsToSitWith || snapshot.evidenceQuestions || snapshot.evidence_questions || snapshot.proofQuestions || snapshot.proof_questions || snapshot.validationQuestions || snapshot.validation_questions || snapshot.decisionQuestions || snapshot.decision_questions || snapshot.questionsToAnswer || snapshot.questions_to_answer || snapshot.followupQuestions || snapshot.followup_questions || snapshot.openQuestions || snapshot.open_questions, sourceSection: 'snapshot.questionsToSitWith' }, + { items: envelope.questions_to_sit_with || envelope.questionsToSitWith || envelope.evidenceQuestions || envelope.evidence_questions || envelope.proofQuestions || envelope.proof_questions || envelope.validationQuestions || envelope.validation_questions || envelope.decisionQuestions || envelope.decision_questions || envelope.questionsToAnswer || envelope.questions_to_answer || envelope.followupQuestions || envelope.followup_questions || envelope.openQuestions || envelope.open_questions, sourceSection: 'ranker-input.questionsToSitWith' }, + { items: featureSet.questions_to_sit_with || featureSet.questionsToSitWith || featureSet.evidenceQuestions || featureSet.evidence_questions || featureSet.proofQuestions || featureSet.proof_questions || featureSet.validationQuestions || featureSet.validation_questions || featureSet.decisionQuestions || featureSet.decision_questions || featureSet.questionsToAnswer || featureSet.questions_to_answer || featureSet.followupQuestions || featureSet.followup_questions || featureSet.openQuestions || featureSet.open_questions, sourceSection: 'feature-set.questionsToSitWith' }, + { items: body.questions_to_sit_with || body.questionsToSitWith || body.evidenceQuestions || body.evidence_questions || body.proofQuestions || body.proof_questions || body.validationQuestions || body.validation_questions || body.decisionQuestions || body.decision_questions || body.questionsToAnswer || body.questions_to_answer || body.followupQuestions || body.followup_questions || body.openQuestions || body.open_questions, sourceSection: 'questionsToSitWith' }, ]); const questionOptions = optionsFromQuestionsToSitWith( questionSource.items,