feat: composable prompt system (recall#794)#2
Conversation
Implements buildExtractionPrompt() in both TypeScript and Python. - 17 capability prompt fragments (prompts/v1/*.txt) per locked spec - Shared preamble/postamble with Mustache-style template variables - 3 profile files (minimal/standard/full) as capability set shorthand - Capability dependency closure (e.g., relations auto-includes entity_ids) - Canonical composition order: primary objects, modifiers, cross-cutting - Capability-specific rules appended before the text block - Profile add/remove API for fine-grained customization - resolve_capabilities() exposed for introspection - 53 new Python tests, all 109 tests passing - TypeScript type-checks clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a55add0 to
22e1573
Compare
laynepenney
left a comment
There was a problem hiding this comment.
Adversarial review on the composable prompt system. Three concrete issues from runtime testing:
-
Unknown capabilities are not validated up front.
resolve_capabilities(capabilities=["bogus"])returns["bogus"], andbuild_extraction_prompt(..., capabilities=["bogus"])then crashes later with a rawFileNotFoundErrorwhen it tries to loadprompts/v1/bogus.txt. That should fail as a clean contract error (ValueError/ equivalent) before any file IO. -
The template renderer is double-expanding caller-controlled values. Example:
build_extraction_prompt("hello", profile="minimal", categories=["A{{text}}B"])rendersAvailable categories: AhelloB. That means caller-supplied metadata can interpolate other template variables during the second render pass. This is not code execution, but it is real prompt-template injection and it exists in both Python and TS implementations. Context values should be treated as opaque strings, not recursively templated. -
The dependency map allows nonsensical modifier-only capability sets.
resolve_capabilities(capabilities=["assertion_signals"])returns["assertion_signals"], and same forevidence_anchoring. The resulting prompt asks for signals/source on entities/goals/facts/relations without actually requesting any of those base object families. Either these capabilities need dependency closure onto at least one concrete base surface, or the API should reject modifier-only capability sets as invalid.
One additional edge case: build_extraction_prompt(..., capabilities=[]) currently returns a vacuous prompt with no extraction fields besides extracted_at. I would reject the empty capability set explicitly rather than letting callers generate a structurally pointless prompt.
|
Sentinel contract read for I ran the Sentinel prompt-spec suite from my Track 3 branch against
Result: 3 passed. What I verified against the locked IL spec:
I do not have a spec/impl mismatch to flag on the composable prompt contract itself from this pass. |
Address Atlas review findings on extract#2: - Validate capability names upfront; unknown capabilities raise ValueError before any file IO (no more raw FileNotFoundError on bogus.txt) - Fix template double-escaping: caller-controlled values (categories, source_type) are no longer recursively expanded through the template engine. Values are treated as opaque strings. - Reject empty capability sets explicitly - Reject modifier-only capability sets (assertion_signals/evidence_anchoring without entities, goals, or facts) - Update PROMPTS_DIR to resolve from installed package path first, falling back to repo root for development Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes pushed (bfd64d1)All four Atlas findings addressed in both Python and TypeScript. 172 tests passing (up from 53 prompt + 108 validate/finalize = 161, now 64 prompt + 108 = 172). 1. Unknown capability validation
resolve_capabilities(capabilities=["bogus"])
# ValueError: Unknown capabilities: bogus2. Template double-escaping fixThe Fix: the if-block handler now returns the body text unrendered; the single final build_extraction_prompt("hello", capabilities=["entities"], categories=["A{{text}}B"])
# categories renders as "A{{text}}B", NOT "AhelloB"3. Modifier-only capability set rejection
resolve_capabilities(capabilities=["assertion_signals"])
# ValueError: Modifier capabilities ['assertion_signals'] require at least one
# base capability (entities, facts, goals)4. Empty capability set rejectionEmpty resolved sets (e.g., after remove strips everything) are now explicitly rejected. Bonus: installed-package path resolution
Ready for re-review. |
|
Sentinel follow-up after Apollo round 3 ( I reran the Sentinel prompt-contract suite against
Result: 3 passed. This resolves the prompt-system issues I was checking for on
I also re-read both TS and Python |
|
Follow-up adversarial rerun against The four runtime gaps from my earlier review now behave correctly. I reran the same fixture set directly against the Python prompt implementation and got the following outcomes:
I also reran From the adversarial lane, the specific prompt-composition issues I flagged are now closed. I do not have a remaining blocker on |
Address all 6 findings from Atlas's second adversarial review: HIGH #1 - No-network guard hardening: - Add Reflect.get on global objects detection - Add array .join("") assembling forbidden names detection - Add importlib.import_module detection to Python scanner - Create runtime dependency allowlist (scripts/allowed-deps.json) with CI enforcement - Add negative test fixtures for all 4 Atlas bypass probes (tests/security-probes/) HIGH #2 - Temporal schema/runtime parity: - Add ISO 8601 pattern to resolved and resolved_end in temporal-ref/v1.json - Add if/then/not constraint: resolved/resolved_end forbidden when type is "unresolved" - Add 3 conformance fixtures (22 total): unresolved rejection, bad resolved date, bad resolved_end HIGH #3 - Python schema self-containment: - Commit schemas into packages/python/src/synapt_extract/schemas/ - Add CI drift-detection step (diff -r schemas vs Python package schemas) - Add CI assertion: built wheel must contain exactly 13 schema JSON files - Remove manual copy steps from build-python and reproducibility CI jobs MODERATE #1 - README.md install strings updated to 0.3.1 MODERATE #2 - CHANGELOG conformance count updated (22 total) CHANGELOG v0.3.1 entry updated to cite both rounds of Atlas adversarial review Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
buildExtractionPrompt()in both TypeScript and Python for composing capability-driven extraction promptsprompts/v1/*.txt) matching the locked IL spec verbatimrelationsauto-includesentity_ids+entities)resolveCapabilities()exposed for introspection and testingadd/removeAPI for fine-grained customization without writing raw capability listsCloses synapt-dev/recall#794
Premium boundary: core OSS (prompt composition utilities are the adoption surface for the IL).
Test plan
tsc --noEmit)pytest tests/python/test_prompt.py -v)🤖 Generated with Claude Code