{"record":{"id":"c62ff04bbca1c513","repo":"Graphify-Labs/graphify","slug":"outcome-must-be-one-of-outcomes-got-outcome-r","errorCode":null,"errorMessage":"outcome must be one of {OUTCOMES}, got {outcome!r}","messagePattern":"outcome must be one of (.+?), got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"graphify/ingest.py","lineNumber":295,"sourceCode":"    memory_dir: Path,\n    query_type: str = \"query\",\n    source_nodes: list[str] | None = None,\n    outcome: str | None = None,\n    correction: str | None = None,\n) -> Path:\n    \"\"\"Save a Q&A result as markdown so it gets extracted into the graph on next --update.\n\n    Files are stored in memory_dir (typically graphify-out/memory/) with YAML frontmatter\n    that graphify's extractor reads as node metadata. This closes the feedback loop:\n    the system grows smarter from both what you add AND what you ask.\n\n    ``outcome`` (one of :data:`OUTCOMES`) and ``correction`` are optional work-memory\n    signals: they are written both to the frontmatter (so `graphify reflect` can\n    aggregate them deterministically) and to an ``## Outcome`` body section (so the\n    signal round-trips into the graph on the next semantic re-extraction).\n    \"\"\"\n    if outcome is not None and outcome not in OUTCOMES:\n        raise ValueError(f\"outcome must be one of {OUTCOMES}, got {outcome!r}\")\n\n    memory_dir = Path(memory_dir)\n    memory_dir.mkdir(parents=True, exist_ok=True)\n\n    now = datetime.now(timezone.utc)\n    slug = re.sub(r\"[^\\w]\", \"_\", question.lower())[:50].strip(\"_\")\n    filename = f\"query_{now.strftime('%Y%m%d_%H%M%S')}_{slug}.md\"\n\n    frontmatter_lines = [\n        \"---\",\n        f'type: \"{query_type}\"',\n        f'date: \"{now.isoformat()}\"',\n        f'question: \"{_yaml_str(question)}\"',\n        'contributor: \"graphify\"',\n    ]\n    if outcome:\n        frontmatter_lines.append(f'outcome: \"{_yaml_str(outcome)}\"')\n    if correction:","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/ingest.py#L277-L313","documentation":"Raised by graphify's Q&A memory writer (save query/Q&A results as markdown in the memory dir) when the optional outcome argument is non-None but not one of the allowed tuple OUTCOMES = ('useful', 'dead_end', 'corrected'). The outcome is a work-memory signal written to both the YAML frontmatter and an '## Outcome' body section, so only the three canonical values round-trip deterministically through `graphify reflect`.","triggerScenarios":"Calling the save-QA function (e.g. graphify's API for recording query outcomes) with outcome='yes', 'bad', 'USEFUL' (case-sensitive), or any free-form string outside the tuple; None is explicitly allowed.","commonSituations":"Scripting the memory loop with ad-hoc labels; UI wrappers mapping their own enum ('positive'/'negative') onto outcome; case mismatches; passing 0/1 integers.","solutions":["Use one of the exact values: 'useful', 'dead_end', or 'corrected' (lowercase, underscore)","Pass None (or omit the argument) when there is no outcome signal","Map external enums at the call site: {'positive': 'useful', 'negative': 'dead_end', 'fix': 'corrected'}"],"exampleFix":"# before\nsave_qa(\"How do hooks work?\", answer, outcome=\"helpful\")\n# ValueError: outcome must be one of ('useful', 'dead_end', 'corrected')\n\n# after\nsave_qa(\"How do hooks work?\", answer, outcome=\"useful\")","handlingStrategy":"type-guard","validationCode":"from graphify.ingest import OUTCOMES  # ('useful', 'dead_end', 'corrected')\n\nif outcome is not None and outcome not in OUTCOMES:\n    raise SystemExit(f\"invalid outcome {outcome!r}; choose from {OUTCOMES}\")","typeGuard":"from typing import Literal\nfrom graphify.ingest import OUTCOMES\n\nOutcome = Literal[\"useful\", \"dead_end\", \"corrected\"]\n\ndef is_valid_outcome(value: object) -> bool:\n    \"\"\"Narrow arbitrary input to the canonical outcome tuple.\"\"\"\n    return value is None or (isinstance(value, str) and value in OUTCOMES)","tryCatchPattern":"try:\n    save_qa(question, answer, outcome=outcome)\nexcept ValueError as e:\n    if \"outcome must be one of\" in str(e):\n        outcome = \"corrected\" if correction else \"useful\"  # sanitize to a default\n        save_qa(question, answer, outcome=outcome, correction=correction)\n    else:\n        raise","preventionTips":["Import OUTCOMES from graphify.ingest and validate against the tuple - don't hardcode the list","Type your call sites with Literal['useful','dead_end','corrected'] so bad values fail static checks","Map external enums/labels to the canonical trio at the system boundary"],"tags":["validation","enum","memory","api-contract"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}