diff --git a/README.md b/README.md index 3ef0eb2..6c03e86 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`, `Validate manually`, `Manual proof`, `Hold for later`, or `Set aside`, with either colon or reader-friendly dash separators (`Continue first: …`, `Validate manually — …`, `Evidence next - …`). Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `validateManually`, `manualProof`, `holdForLater`, and `setAside`; whole-payload wrappers may use `rankPayload` / `rank_payload` or `scattermindPayload` / `scattermind_payload`, and already-laned objects may be named `rankReadyBuildOrder` / `rank_ready_build_order` as a more explicit Scattermind handoff alias. Those section values may be arrays, a single candidate object, or a reader-friendly string/newline list; Ranker splits scalar sections into candidate actions so Scattermind does not have to wrap every lane in arrays. 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`), evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `proofQuestions` / `proof_questions`, `validationQuestions` / `validation_questions`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`), and assumption-test arrays (`assumptionTests` / `assumption_tests`, `riskiestAssumptions` / `riskiest_assumptions`, `risksToTest` / `risks_to_test`) that default into Validate next. 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. +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`, `Validate manually`, `Manual proof`, `Hold for later`, or `Set aside`, with either colon or reader-friendly dash separators (`Continue first: …`, `Validate manually — …`, `Evidence next - …`). Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `validateManually`, `manualProof`, `holdForLater`, and `setAside`; whole-payload wrappers may use `rankPayload` / `rank_payload` or `scattermindPayload` / `scattermind_payload`, and already-laned objects may be named `rankReadyBuildOrder` / `rank_ready_build_order` as a more explicit Scattermind handoff alias. Those section values may be arrays, a single candidate object, or a reader-friendly string/newline list; Ranker splits scalar sections into candidate actions so Scattermind does not have to wrap every lane in arrays. 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`), evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `proofQuestions` / `proof_questions`, `proofPlan` / `proof_plan`, `validationQuestions` / `validation_questions`, `validationPlan` / `validation_plan`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`), and assumption-test arrays (`assumptionTests` / `assumption_tests`, `riskiestAssumptions` / `riskiest_assumptions`, `risksToTest` / `risks_to_test`) that default into Validate next. 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/public/app.js b/public/app.js index a23a162..9907677 100644 --- a/public/app.js +++ b/public/app.js @@ -81,7 +81,7 @@ function parsePastedJsonPayload(value) { || 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) || Array.isArray(parsed.threads_to_hold) || Array.isArray(parsed.threadsToHold) || Array.isArray(parsed.actionThreads) || Array.isArray(parsed.action_threads) - || Array.isArray(parsed.questions_to_sit_with) || Array.isArray(parsed.questionsToSitWith) || Array.isArray(parsed.evidenceQuestions) || Array.isArray(parsed.evidence_questions) || Array.isArray(parsed.proofQuestions) || Array.isArray(parsed.proof_questions) || Array.isArray(parsed.validationQuestions) || Array.isArray(parsed.validation_questions) || Array.isArray(parsed.decisionQuestions) || Array.isArray(parsed.decision_questions) || Array.isArray(parsed.questionsToAnswer) || Array.isArray(parsed.questions_to_answer) || Array.isArray(parsed.followupQuestions) || Array.isArray(parsed.followup_questions) || Array.isArray(parsed.openQuestions) || Array.isArray(parsed.open_questions) + || Array.isArray(parsed.questions_to_sit_with) || Array.isArray(parsed.questionsToSitWith) || Array.isArray(parsed.evidenceQuestions) || Array.isArray(parsed.evidence_questions) || Array.isArray(parsed.proofQuestions) || Array.isArray(parsed.proof_questions) || Array.isArray(parsed.proofPlan) || Array.isArray(parsed.proof_plan) || Array.isArray(parsed.validationQuestions) || Array.isArray(parsed.validation_questions) || Array.isArray(parsed.validationPlan) || Array.isArray(parsed.validation_plan) || Array.isArray(parsed.decisionQuestions) || Array.isArray(parsed.decision_questions) || Array.isArray(parsed.questionsToAnswer) || Array.isArray(parsed.questions_to_answer) || Array.isArray(parsed.followupQuestions) || Array.isArray(parsed.followup_questions) || Array.isArray(parsed.openQuestions) || Array.isArray(parsed.open_questions) || Array.isArray(parsed.assumptionTests) || Array.isArray(parsed.assumption_tests) || Array.isArray(parsed.riskiestAssumptions) || Array.isArray(parsed.riskiest_assumptions) || Array.isArray(parsed.risksToTest) || Array.isArray(parsed.risks_to_test) ); return looksLikeBridgePayload ? parsed : null; diff --git a/public/index.html b/public/index.html index 9183460..020fe56 100644 --- a/public/index.html +++ b/public/index.html @@ -4,9 +4,9 @@ - + Ranker — feedback front door for messy decisions - +
@@ -127,6 +127,6 @@ Or paste a Scattermind Concept Map JSON object here; Ranker will preserve source
- + diff --git a/scripts/check-rank-feedback.mjs b/scripts/check-rank-feedback.mjs index d6c0fe2..3b235c6 100644 --- a/scripts/check-rank-feedback.mjs +++ b/scripts/check-rank-feedback.mjs @@ -2520,7 +2520,37 @@ try { assert.equal(rankedBuildOrderAlias.handoff.readiness.status, 'ready'); assert.deepEqual(rankedBuildOrderAlias.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, threadsFallbackTop: threadsFallback.ranked[0].id, questionsFallbackTop: questionsFallback.ranked[0].id, freeSnapshotTop: freeSnapshot.ranked[0].id, storedScattermindRowTop: storedScattermindRow.ranked[0].id, candidateActionsAliasTop: candidateActionsAlias.ranked[0].id, rankReadyActionsEnvelopeTop: rankReadyActionsEnvelope.ranked[0].id, continuationEnvelopeTop: continuationEnvelope.ranked[0].id, buildOrderPreviewTop: buildOrderPreview.ranked[0].id, scattermindRoadmapLanguageTop: scattermindRoadmapLanguage.ranked[0].id, scattermindRoadmapLensOnlyTop: scattermindRoadmapLensOnly.ranked[0].id, stringifiedRankerInputTop: stringifiedRankerInput.ranked[0].id, scalarBuildOrderSectionsTop: scalarBuildOrderSections.ranked[0].id, snakeReaderFriendlyBuildOrderTop: snakeReaderFriendlyBuildOrder.ranked[0].id, readerFriendlyLensLabelTop: readerFriendlyLensLabel.ranked[0].id, gameRouteGuardrailTop: gameRouteGuardrail.ranked[0].id, rankedBuildOrderAliasTop: rankedBuildOrderAlias.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 proofPlanAliasResponse = await fetch(`${base}/api/rank-feedback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sourceName: 'Scattermind', + referenceCode: 'SM-PROOF-PLAN-ALIAS-1', + working_name: 'Proof Plan alias export', + ideaText: 'Scattermind exported a reader-facing proof plan and validation plan instead of software feature names.', + context: 'Solo builder. Manual proof first. Avoid account dashboards and saved workspaces until proof.', + mode: 'validation', + conceptMap: { + doFirst: [{ id: 'proof-plan-active', action: 'One-call source-traced proof offer', evidence_needed: 'Can one target user explain the first move after reading the receipt?', source_item_id: 'proof-plan-active-1', source_title: 'Build Order', ranker_hints: { value: 8, effort: 2, confidence: 7, urgency: 8, risk: 2 } }], + proof_plan: [{ id: 'proof-plan-call', action: 'Run the first proof call', evidence_needed: 'Does a tired user understand the active slice without a dashboard?', source_item_id: 'proof-plan-1', source_title: 'Proof Plan' }], + validation_plan: [{ id: 'validation-plan-recheck', action: 'Re-rank after the proof call', evidence_needed: 'Did the first proof change the defended build order?', source_item_id: 'validation-plan-1', source_title: 'Validation Plan' }], + parkingLot: [{ id: 'proof-plan-dashboard', action: 'Saved proof dashboard with accounts and billing', evidence_needed: 'Not before proof.', source_item_id: 'parking-1', source_title: 'Parking Lot' }], + }, + }), + }); + assert.equal(proofPlanAliasResponse.status, 200); + const proofPlanAlias = await proofPlanAliasResponse.json(); + assert.equal(proofPlanAlias.input.optionCount, 4, 'proof_plan / validation_plan should become validate-next candidates without renaming them to features'); + assert.equal(proofPlanAlias.ranked[0].id, 'proof-plan-active'); + assert.equal(proofPlanAlias.ranked.find(item => item.id === 'proof-plan-call').lane.id, 'test'); + assert.equal(proofPlanAlias.ranked.find(item => item.id === 'validation-plan-recheck').lane.id, 'test'); + assert.equal(proofPlanAlias.ranked.find(item => item.id === 'proof-plan-dashboard').lane.id, 'park'); + assert.equal(proofPlanAlias.ranked.find(item => item.id === 'proof-plan-call').provenance.sourceSection, 'concept-map.experiments'); + assert.equal(proofPlanAlias.handoff.readiness.status, 'ready'); + assert.deepEqual(proofPlanAlias.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, threadsFallbackTop: threadsFallback.ranked[0].id, questionsFallbackTop: questionsFallback.ranked[0].id, freeSnapshotTop: freeSnapshot.ranked[0].id, storedScattermindRowTop: storedScattermindRow.ranked[0].id, candidateActionsAliasTop: candidateActionsAlias.ranked[0].id, rankReadyActionsEnvelopeTop: rankReadyActionsEnvelope.ranked[0].id, continuationEnvelopeTop: continuationEnvelope.ranked[0].id, buildOrderPreviewTop: buildOrderPreview.ranked[0].id, scattermindRoadmapLanguageTop: scattermindRoadmapLanguage.ranked[0].id, scattermindRoadmapLensOnlyTop: scattermindRoadmapLensOnly.ranked[0].id, stringifiedRankerInputTop: stringifiedRankerInput.ranked[0].id, scalarBuildOrderSectionsTop: scalarBuildOrderSections.ranked[0].id, snakeReaderFriendlyBuildOrderTop: snakeReaderFriendlyBuildOrder.ranked[0].id, readerFriendlyLensLabelTop: readerFriendlyLensLabel.ranked[0].id, gameRouteGuardrailTop: gameRouteGuardrail.ranked[0].id, rankedBuildOrderAliasTop: rankedBuildOrderAlias.ranked[0].id, proofPlanAliasTop: proofPlanAlias.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 { server.kill('SIGTERM'); } diff --git a/server.js b/server.js index 752ee0e..b92f6e0 100644 --- a/server.js +++ b/server.js @@ -668,8 +668,12 @@ function looksLikeRankPayload(value = {}) { || Array.isArray(value.evidence_questions) || Array.isArray(value.proofQuestions) || Array.isArray(value.proof_questions) + || Array.isArray(value.proofPlan) + || Array.isArray(value.proof_plan) || Array.isArray(value.validationQuestions) || Array.isArray(value.validation_questions) + || Array.isArray(value.validationPlan) + || Array.isArray(value.validation_plan) || Array.isArray(value.decisionQuestions) || Array.isArray(value.decision_questions) || Array.isArray(value.questionsToAnswer) @@ -1195,7 +1199,7 @@ function buildOrderSectionGroup(buildOrder = {}, baseSection = 'buildOrder') { const source = objectFrom(buildOrder); return compactCandidateGroup([ { items: source.doFirst || source.do_first || source.buildFirst || source.build_first || source.buildNow || source.build_now || source.now || source.continueFirst || source.continue_first || source.makeTangible || source.make_tangible || source.startHere || source.start_here || source.startNow || source.start_now || source.shipFirst || source.ship_first, sourceSection: `${baseSection}.doFirst`, defaultLane: 'do-first' }, - { items: source.validateNext || source.validate_next || source.validateManually || source.validate_manually || source.testNext || source.test_next || source.testManually || source.test_manually || source.manualProof || source.manual_proof || source.validation || source.evidenceNext || source.evidence_next || source.tryNext || source.try_next || source.learnNext || source.learn_next || source.proofStep || source.proof_step || source.proofSteps || source.proof_steps, sourceSection: `${baseSection}.validateNext`, defaultLane: 'validate-next' }, + { items: source.validateNext || source.validate_next || source.validateManually || source.validate_manually || source.testNext || source.test_next || source.testManually || source.test_manually || source.manualProof || source.manual_proof || source.validation || source.validationPlan || source.validation_plan || source.evidenceNext || source.evidence_next || source.tryNext || source.try_next || source.learnNext || source.learn_next || source.proofStep || source.proof_step || source.proofSteps || source.proof_steps || source.proofPlan || source.proof_plan, sourceSection: `${baseSection}.validateNext`, defaultLane: 'validate-next' }, { items: source.defer || source.deferred || source.later || source.afterProof || source.after_proof || source.holdForLater || source.hold_for_later || source.notYet || source.not_yet || source.leaveOut || source.leave_out || source.skipForNow || source.skip_for_now || source.doNotBuildYet || source.do_not_build_yet, sourceSection: `${baseSection}.defer`, defaultLane: 'defer' }, { items: source.park || source.parkingLot || source.parking_lot || source.parked || source.probablyNoise || source.probably_noise || source.noise || source.setAside || source.set_aside || source.outOfScope || source.out_of_scope || source.cut || source.distractions, sourceSection: `${baseSection}.park`, defaultLane: 'park' }, ]); @@ -1495,16 +1499,16 @@ function optionsFromBody(body = {}) { { 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.validationTests || body.validation_tests, sourceSection: 'experiments', defaultLane: 'validate-next' }, - { items: body.proofTests || body.proof_tests, sourceSection: 'experiments', defaultLane: 'validate-next' }, + { items: body.validationTests || body.validation_tests || body.validationPlan || body.validation_plan, sourceSection: 'experiments', defaultLane: 'validate-next' }, + { items: body.proofTests || body.proof_tests || body.proofPlan || body.proof_plan, sourceSection: 'experiments', defaultLane: 'validate-next' }, { items: body.assumptionTests || body.assumption_tests || body.riskiestAssumptions || body.riskiest_assumptions || body.risksToTest || body.risks_to_test, sourceSection: 'assumptionTests', defaultLane: 'validate-next' }, { items: envelope.experiments, sourceSection: 'ranker-input.experiments', defaultLane: 'validate-next' }, - { items: envelope.validationTests || envelope.validation_tests, sourceSection: 'ranker-input.experiments', defaultLane: 'validate-next' }, - { items: envelope.proofTests || envelope.proof_tests, sourceSection: 'ranker-input.experiments', defaultLane: 'validate-next' }, + { items: envelope.validationTests || envelope.validation_tests || envelope.validationPlan || envelope.validation_plan, sourceSection: 'ranker-input.experiments', defaultLane: 'validate-next' }, + { items: envelope.proofTests || envelope.proof_tests || envelope.proofPlan || envelope.proof_plan, sourceSection: 'ranker-input.experiments', defaultLane: 'validate-next' }, { items: envelope.assumptionTests || envelope.assumption_tests || envelope.riskiestAssumptions || envelope.riskiest_assumptions || envelope.risksToTest || envelope.risks_to_test, sourceSection: 'ranker-input.assumptionTests', 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.proofTests || featureSet.proof_tests, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' }, + { items: featureSet.validationTests || featureSet.validation_tests || featureSet.validationPlan || featureSet.validation_plan, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' }, + { items: featureSet.proofTests || featureSet.proof_tests || featureSet.proofPlan || featureSet.proof_plan, sourceSection: 'feature-set.experiments', defaultLane: 'validate-next' }, { items: featureSet.assumptionTests || featureSet.assumption_tests || featureSet.riskiestAssumptions || featureSet.riskiest_assumptions || featureSet.risksToTest || featureSet.risks_to_test, sourceSection: 'feature-set.assumptionTests', defaultLane: 'validate-next' }, { items: body.validateNext || body.validate_next || body.validateManually || body.validate_manually || body.validate || body.validation || body.evidenceNext || body.evidence_next || body.tryNext || body.try_next || body.learnNext || body.learn_next || body.testManually || body.test_manually || body.manualProof || body.manual_proof, sourceSection: 'validateNext', defaultLane: 'validate-next' }, { 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' }, @@ -1527,8 +1531,8 @@ function optionsFromBody(body = {}) { { 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.validateManually || conceptMap.validate_manually || conceptMap.validate || conceptMap.validation || conceptMap.evidenceNext || conceptMap.evidence_next || conceptMap.tryNext || conceptMap.try_next || conceptMap.learnNext || conceptMap.learn_next || conceptMap.testManually || conceptMap.test_manually || conceptMap.manualProof || conceptMap.manual_proof, sourceSection: 'concept-map.validateNext', 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.proofTests || conceptMap.proof_tests, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' }, + { items: conceptMap.validationTests || conceptMap.validation_tests || conceptMap.validationPlan || conceptMap.validation_plan, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' }, + { items: conceptMap.proofTests || conceptMap.proof_tests || conceptMap.proofPlan || conceptMap.proof_plan, sourceSection: 'concept-map.experiments', defaultLane: 'validate-next' }, { items: conceptMap.assumptionTests || conceptMap.assumption_tests || conceptMap.riskiestAssumptions || conceptMap.riskiest_assumptions || conceptMap.risksToTest || conceptMap.risks_to_test, sourceSection: 'concept-map.assumptionTests', defaultLane: 'validate-next' }, { 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 || conceptMap.probablyNoise || conceptMap.probably_noise || conceptMap.setAside || conceptMap.set_aside || conceptMap.outOfScope || conceptMap.out_of_scope, sourceSection: 'concept-map.parkingLot', defaultLane: 'park' }, @@ -1545,8 +1549,8 @@ function optionsFromBody(body = {}) { { 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.validateManually || snapshot.validate_manually || snapshot.validate || snapshot.validation || snapshot.evidenceNext || snapshot.evidence_next || snapshot.tryNext || snapshot.try_next || snapshot.learnNext || snapshot.learn_next || snapshot.testManually || snapshot.test_manually || snapshot.manualProof || snapshot.manual_proof, sourceSection: 'snapshot.validateNext', 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.proofTests || snapshot.proof_tests, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' }, + { items: snapshot.validationTests || snapshot.validation_tests || snapshot.validationPlan || snapshot.validation_plan, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' }, + { items: snapshot.proofTests || snapshot.proof_tests || snapshot.proofPlan || snapshot.proof_plan, sourceSection: 'snapshot.experiments', defaultLane: 'validate-next' }, { items: snapshot.assumptionTests || snapshot.assumption_tests || snapshot.riskiestAssumptions || snapshot.riskiest_assumptions || snapshot.risksToTest || snapshot.risks_to_test, sourceSection: 'snapshot.assumptionTests', defaultLane: 'validate-next' }, { 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 || snapshot.probablyNoise || snapshot.probably_noise || snapshot.setAside || snapshot.set_aside || snapshot.outOfScope || snapshot.out_of_scope, sourceSection: 'snapshot.parkingLot', defaultLane: 'park' },