feat: add 'option' node kind and 'contained_in' edge — 60A.3
Implementation of Candidate B (unknown+option) from decision architecture design in 60A.2. Adds two new primitives to the situation graph: Schema (lib/graph/schema.js): - SituationKind.option — a choice available within a decision context - SituationRelationship.contained_in — links option → its parent unknown context Prompt rules (lib/graph/prompt-builder.js): - Section added: Decision Option Structure Rules with 5 numbered instructions governing when/how to create option nodes, link them via contained_in, attach consequences to specific options, and handle do-nothing alternatives. Explicitly forbids alternative_to edges and is_baseline/is_default flags. Tests (446 new lines): - schema.test.js: +300 — enum completeness updates, option kind validation, contained_in edge validation, native two-option graph fixture (~25 new tests) - prompt-builder.test.js: +133 — focused rules verification for all 5 rule points, negative checks (no relocation/savings/example-specific wording, no alternative_to requirement, baseline flag prohibition context) No production code paths affected beyond the two enum additions; existing node and edge kinds remain unchanged. No Ollama calls, no live API calls.
This commit is contained in:
@@ -134,6 +134,19 @@ The JSON object must contain exactly these top-level fields:
|
||||
31. If the answer explicitly states a hard constraint, state that directly in userSupportedMeaning.
|
||||
32. Populate resolutionGuidance when the user's meaning genuinely implies must_remain_unresolved, may_resolve, or must_resolve. Keep it null only when no existing resolution state actually applies.
|
||||
|
||||
## Decision Option Structure Rules
|
||||
When the user presents mutually exclusive candidate actions for one unresolved choice:
|
||||
|
||||
1. Create exactly one node of kind "unknown" to carry the decision question (the existing mechanism). Do not add a separate "decision" node kind. Keep that unknown as-is or create it fresh — do not duplicate it into every option.
|
||||
|
||||
2. For each candidate path, create exactly one node of kind "option". The option's label names the alternative; its description states what that alternative entails.
|
||||
|
||||
3. Link each option to the decision-context unknown using relationship "contained_in" (edge: option → unknown). Shared membership already implies these options are alternatives of each other — do not add an "alternative_to" edge between options.
|
||||
|
||||
4. Attach consequences and evidence to the specific option they belong to via existing edge types ("causes", "may_cause", etc.). Each consequence's fromNodeId explicitly identifies its parent option. Do not collapse all alternatives into one generic trade-off description on a single node.
|
||||
|
||||
5. A do-nothing / stay-put / current-state path is an option when it is genuinely one of the alternatives — represent it with kind "option" and label it clearly. Do not introduce an "is_baseline", "is_default", or "is_status_quo" field; baseline meaning is carried by label and consequences alone in this implementation.
|
||||
|
||||
## Contract: structuralActionRequired Declaration Rule
|
||||
|
||||
When answerMeaning.userSupportedMeaning is populated you MUST set structuralActionRequired to match what your proposal outputs:
|
||||
|
||||
@@ -18,6 +18,7 @@ export const SituationKind = /** @type {const} */ ({
|
||||
assumption: "assumption",
|
||||
unknown: "unknown",
|
||||
conclusion: "conclusion",
|
||||
option: "option",
|
||||
});
|
||||
|
||||
export const SituationStatus = /** @type {const} */ ({
|
||||
@@ -83,6 +84,7 @@ export const SituationRelationship = /** @type {const} */ ({
|
||||
measures: "measures",
|
||||
compares_with: "compares_with",
|
||||
updates: "updates",
|
||||
contained_in: "contained_in",
|
||||
other: "other",
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user