pen-stack 6.13.0__tar.gz → 6.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. {pen_stack-6.13.0 → pen_stack-6.14.0}/CHANGELOG.md +34 -0
  2. {pen_stack-6.13.0 → pen_stack-6.14.0}/CITATION.cff +1 -1
  3. {pen_stack-6.13.0 → pen_stack-6.14.0}/PKG-INFO +3 -1
  4. pen_stack-6.14.0/benchmarks/writespec/SHA256SUMS +4 -0
  5. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/offtarget.md +4 -2
  6. pen_stack-6.14.0/docs/writespec_bench.md +44 -0
  7. pen_stack-6.14.0/docs/writespec_profile.md +58 -0
  8. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/__init__.py +1 -1
  9. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/mcp_server.py +12 -0
  10. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/api/manifest.py +6 -0
  11. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/server/api.py +12 -0
  12. pen_stack-6.14.0/pen_stack/spec/__init__.py +18 -0
  13. pen_stack-6.14.0/pen_stack/spec/clarify.py +42 -0
  14. pen_stack-6.14.0/pen_stack/spec/extract.py +232 -0
  15. pen_stack-6.14.0/pen_stack/spec/resolvers/__init__.py +17 -0
  16. pen_stack-6.14.0/pen_stack/spec/resolvers/cell.py +51 -0
  17. pen_stack-6.14.0/pen_stack/spec/resolvers/chem.py +32 -0
  18. pen_stack-6.14.0/pen_stack/spec/resolvers/feature.py +36 -0
  19. pen_stack-6.14.0/pen_stack/spec/resolvers/gene.py +43 -0
  20. pen_stack-6.14.0/pen_stack/spec/resolvers/locus.py +29 -0
  21. pen_stack-6.14.0/pen_stack/spec/resolvers/phenotype.py +37 -0
  22. pen_stack-6.14.0/pen_stack/spec/satisfy.py +114 -0
  23. pen_stack-6.14.0/pen_stack/spec/service.py +33 -0
  24. pen_stack-6.14.0/pen_stack/spec/writespec.py +244 -0
  25. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/offtarget_predict.py +4 -2
  26. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack.egg-info/PKG-INFO +3 -1
  27. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack.egg-info/SOURCES.txt +18 -0
  28. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack.egg-info/requires.txt +3 -0
  29. pen_stack-6.14.0/prereg/SHA256_LOCK_ws_writespec.json +8 -0
  30. pen_stack-6.14.0/prereg/ws_writespec.yaml +50 -0
  31. {pen_stack-6.13.0 → pen_stack-6.14.0}/pyproject.toml +2 -1
  32. {pen_stack-6.13.0 → pen_stack-6.14.0}/LICENSE +0 -0
  33. {pen_stack-6.13.0 → pen_stack-6.14.0}/MANIFEST.in +0 -0
  34. {pen_stack-6.13.0 → pen_stack-6.14.0}/README.md +0 -0
  35. {pen_stack-6.13.0 → pen_stack-6.14.0}/bench/run.py +0 -0
  36. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/delivery/SHA256SUMS +0 -0
  37. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/genome_writing_bench/LEADERBOARD.md +0 -0
  38. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/genome_writing_bench/README.md +0 -0
  39. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/genome_writing_bench/SHA256SUMS +0 -0
  40. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/genome_writing_bench/SUBMISSIONS.md +0 -0
  41. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/genome_writing_bench/tasks.yaml +0 -0
  42. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/genome_writing_challenge/README.md +0 -0
  43. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/genome_writing_challenge/SUBMISSIONS.md +0 -0
  44. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/offtarget/SHA256SUMS +0 -0
  45. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/oracle/SHA256SUMS +0 -0
  46. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/position_effect/README.md +0 -0
  47. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/position_effect/SHA256SUMS +0 -0
  48. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/verify/SHA256SUMS +0 -0
  49. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/writer_efficiency/README.md +0 -0
  50. {pen_stack-6.13.0 → pen_stack-6.14.0}/benchmarks/writer_efficiency/SHA256SUMS +0 -0
  51. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/aav_serotype_tropism.yaml +0 -0
  52. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/antipeg.yaml +0 -0
  53. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/atlas_families.yaml +0 -0
  54. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/bridge_offtarget_profile.yaml +0 -0
  55. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/calibration/preexisting_nab_independent.yaml +0 -0
  56. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/capsid_epitope_oracle.yaml +0 -0
  57. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/capsid_sequences.fasta +0 -0
  58. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/cargo_polish.yaml +0 -0
  59. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/cell_types.yaml +0 -0
  60. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/datasets.yaml +0 -0
  61. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/delivery_constraints.yaml +0 -0
  62. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/delivery_rules.yaml +0 -0
  63. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/delivery_vehicles.yaml +0 -0
  64. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/expression/modifiers.yaml +0 -0
  65. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/expression/promoters.yaml +0 -0
  66. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/gates_v3.yaml +0 -0
  67. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/genotoxicity_oracle.yaml +0 -0
  68. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/gsh_validated_heldout.yaml +0 -0
  69. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/intent_weights.yaml +0 -0
  70. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/known_unknowns.yaml +0 -0
  71. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/llm.yaml +0 -0
  72. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/metric_guide.yaml +0 -0
  73. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/mhc_epitope_oracle.yaml +0 -0
  74. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/monitor_queries.yaml +0 -0
  75. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/oracles/execution.yaml +0 -0
  76. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/oracles/reliability.yaml +0 -0
  77. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/oracles/scope_cards.yaml +0 -0
  78. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/rules/delivery.yaml +0 -0
  79. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/rules/fold.yaml +0 -0
  80. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/rules/multiplex.yaml +0 -0
  81. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/rules/payload.yaml +0 -0
  82. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/rules/reachability.yaml +0 -0
  83. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/safety/hazard_registry.yaml +0 -0
  84. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/safety/policy.yaml +0 -0
  85. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/safety/probes.yaml +0 -0
  86. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/score_axes.yaml +0 -0
  87. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/seroprevalence.yaml +0 -0
  88. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/target_sites.yaml +0 -0
  89. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/universe_crosswalk.yaml +0 -0
  90. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/write_types.yaml +0 -0
  91. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/writer_sequences.fasta +0 -0
  92. {pen_stack-6.13.0 → pen_stack-6.14.0}/configs/wtkb_curated.yaml +0 -0
  93. {pen_stack-6.13.0 → pen_stack-6.14.0}/data/curated/bridge_offtarget_energetics.json +0 -0
  94. {pen_stack-6.13.0 → pen_stack-6.14.0}/data/curated/bridge_offtarget_profile_measured.parquet +0 -0
  95. {pen_stack-6.13.0 → pen_stack-6.14.0}/data/curated/gene_coords.parquet +0 -0
  96. {pen_stack-6.13.0 → pen_stack-6.14.0}/data/curated/unified_editor_universe.parquet +0 -0
  97. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/BACKLOG.md +0 -0
  98. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/DEPLOY.md +0 -0
  99. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/INFRA.md +0 -0
  100. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/MCP.md +0 -0
  101. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/RELEASING.md +0 -0
  102. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/REPRO.md +0 -0
  103. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/STABILITY.md +0 -0
  104. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/agent.md +0 -0
  105. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/alphagenome_feasibility.md +0 -0
  106. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/autonomy.md +0 -0
  107. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/benchmark_circularity.md +0 -0
  108. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/biosecurity.md +0 -0
  109. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/build_interface.md +0 -0
  110. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/cards/atlas.md +0 -0
  111. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/cards/durability.md +0 -0
  112. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/cards/offtarget_data.md +0 -0
  113. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/cards/position_effect_data.md +0 -0
  114. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/cards/safety.md +0 -0
  115. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/cards/writer_efficiency_data.md +0 -0
  116. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/challenge.md +0 -0
  117. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/closed_loop.md +0 -0
  118. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/co_scientist.md +0 -0
  119. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/co_scientist_loop.md +0 -0
  120. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/delivery.md +0 -0
  121. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/delivery_immunology.md +0 -0
  122. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/delivery_recommender.md +0 -0
  123. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/digital_twin.md +0 -0
  124. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/dissemination.md +0 -0
  125. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/environment.md +0 -0
  126. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/experiment_design.md +0 -0
  127. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/generative_design.md +0 -0
  128. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/immune_profiler.md +0 -0
  129. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/index.md +0 -0
  130. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/integrations.md +0 -0
  131. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/live_oracles.md +0 -0
  132. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/mechanistic_constraints.md +0 -0
  133. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/oracle_mesh.md +0 -0
  134. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/oracles.md +0 -0
  135. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/position_effect.md +0 -0
  136. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/positioning.md +0 -0
  137. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/private_data_formats.md +0 -0
  138. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/quickstart.md +0 -0
  139. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/responsible_use.md +0 -0
  140. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/rule_spec.md +0 -0
  141. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/rules.md +0 -0
  142. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/scope.md +0 -0
  143. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/scorecard.md +0 -0
  144. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/tpe_bench.md +0 -0
  145. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/tutorials/compare-families.md +0 -0
  146. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/tutorials/score-deliverability.md +0 -0
  147. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/tutorials/where-can-i-write.md +0 -0
  148. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/tutorials/which-writer-reaches-locus.md +0 -0
  149. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/uncertainty.md +0 -0
  150. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/verify.md +0 -0
  151. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/verify_service.md +0 -0
  152. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/world_model.md +0 -0
  153. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/writer_efficiency.md +0 -0
  154. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/writer_verification.md +0 -0
  155. {pen_stack-6.13.0 → pen_stack-6.14.0}/docs/wtkb.md +0 -0
  156. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/_resources.py +0 -0
  157. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/active/__init__.py +0 -0
  158. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/active/acquire.py +0 -0
  159. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/active/design.py +0 -0
  160. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/active/validate.py +0 -0
  161. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/adapt/__init__.py +0 -0
  162. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/adapt/finetune.py +0 -0
  163. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/adapt/ingest.py +0 -0
  164. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/adapt/pipeline.py +0 -0
  165. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/adapt/recalibrate.py +0 -0
  166. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/adapt/report.py +0 -0
  167. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/__init__.py +0 -0
  168. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/cite.py +0 -0
  169. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/co_scientist.py +0 -0
  170. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/epistemic.py +0 -0
  171. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/guardrails.py +0 -0
  172. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/orchestrator.py +0 -0
  173. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/orchestrator_live.py +0 -0
  174. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/pen_agent.py +0 -0
  175. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/scope.py +0 -0
  176. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/agent/tools.py +0 -0
  177. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/api/__init__.py +0 -0
  178. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/__init__.py +0 -0
  179. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/build_wtkb.py +0 -0
  180. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/crosslink.py +0 -0
  181. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/expand.py +0 -0
  182. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/guide_design.py +0 -0
  183. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/schema.py +0 -0
  184. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/scorecard.py +0 -0
  185. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/universe.py +0 -0
  186. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/variant_propose.py +0 -0
  187. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/writer_efficiency.py +0 -0
  188. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/writer_predict.py +0 -0
  189. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/writer_recommend.py +0 -0
  190. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/atlas/writer_verify.py +0 -0
  191. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/__init__.py +0 -0
  192. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/activity.py +0 -0
  193. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/cli.py +0 -0
  194. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/fold_qc.py +0 -0
  195. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/guide_qc.py +0 -0
  196. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/ingest.py +0 -0
  197. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/offtarget.py +0 -0
  198. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/offtarget_energetics.py +0 -0
  199. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/ortholog_screen.py +0 -0
  200. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/bridge/pipeline.py +0 -0
  201. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/build/__init__.py +0 -0
  202. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/build/ingest.py +0 -0
  203. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/build/protocol.py +0 -0
  204. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/build/simlab.py +0 -0
  205. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/cli.py +0 -0
  206. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/data/__init__.py +0 -0
  207. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/data/encode.py +0 -0
  208. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/data/genome.py +0 -0
  209. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/data/ingest_chromatin.py +0 -0
  210. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/data/ingest_integration.py +0 -0
  211. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/data/ingest_safety_annot.py +0 -0
  212. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/data/ingest_trip.py +0 -0
  213. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/design/__init__.py +0 -0
  214. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/design/capsid_generate.py +0 -0
  215. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/design/generate.py +0 -0
  216. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/design/pareto.py +0 -0
  217. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/design/space.py +0 -0
  218. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/design/writer_variants.py +0 -0
  219. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/env/__init__.py +0 -0
  220. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/env/genome_writing_env.py +0 -0
  221. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/env/policies.py +0 -0
  222. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/graph/__init__.py +0 -0
  223. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/graph/build.py +0 -0
  224. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/graph/cell_types.py +0 -0
  225. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/graph/ingest.py +0 -0
  226. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/graph/query.py +0 -0
  227. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/graph/schema.py +0 -0
  228. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/loop/__init__.py +0 -0
  229. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/loop/continual.py +0 -0
  230. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/loop/cycle.py +0 -0
  231. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/loop/drift.py +0 -0
  232. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/mech/__init__.py +0 -0
  233. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/mech/classify_atlas.py +0 -0
  234. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/mech/whitelist.py +0 -0
  235. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/monitor/__init__.py +0 -0
  236. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/monitor/europepmc.py +0 -0
  237. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/monitor/run.py +0 -0
  238. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/monitor/triage.py +0 -0
  239. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/__init__.py +0 -0
  240. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/affinity.py +0 -0
  241. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/cache.py +0 -0
  242. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/energetics.py +0 -0
  243. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/genome.py +0 -0
  244. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/protein_design.py +0 -0
  245. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/reliability.py +0 -0
  246. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/rna.py +0 -0
  247. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/schema.py +0 -0
  248. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/status.py +0 -0
  249. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/structure.py +0 -0
  250. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/structure_run.py +0 -0
  251. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/oracles/vcell.py +0 -0
  252. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/__init__.py +0 -0
  253. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/ada_risk.py +0 -0
  254. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/antipeg_oracle.py +0 -0
  255. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/capsid_epitope_oracle.py +0 -0
  256. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/cargo.py +0 -0
  257. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/cargo_polish.py +0 -0
  258. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/delivery.py +0 -0
  259. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/delivery_constraints.py +0 -0
  260. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/delivery_immune.py +0 -0
  261. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/delivery_immunology.py +0 -0
  262. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/delivery_predict.py +0 -0
  263. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/delivery_vehicles.py +0 -0
  264. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/genotoxicity_oracle.py +0 -0
  265. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/immune_mhc2.py +0 -0
  266. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/immune_profile.py +0 -0
  267. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/innate_sensing.py +0 -0
  268. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/multiplex.py +0 -0
  269. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/optimize.py +0 -0
  270. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/pipeline.py +0 -0
  271. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/report.py +0 -0
  272. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/router.py +0 -0
  273. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/seroprevalence_oracle.py +0 -0
  274. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/planner/target_site.py +0 -0
  275. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rag/__init__.py +0 -0
  276. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rag/index.py +0 -0
  277. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rag/llm.py +0 -0
  278. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rag/qa.py +0 -0
  279. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rules/__init__.py +0 -0
  280. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rules/evaluators.py +0 -0
  281. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rules/loader.py +0 -0
  282. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rules/schema.py +0 -0
  283. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rules/solver.py +0 -0
  284. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/rules/spec.py +0 -0
  285. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/__init__.py +0 -0
  286. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/audit.py +0 -0
  287. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/gate.py +0 -0
  288. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/policy.py +0 -0
  289. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/redteam.py +0 -0
  290. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/registry.py +0 -0
  291. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/screen.py +0 -0
  292. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/safety/standards.py +0 -0
  293. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/score/__init__.py +0 -0
  294. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/score/recalibrate.py +0 -0
  295. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/score/therapeutic.py +0 -0
  296. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/server/__init__.py +0 -0
  297. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/twin/__init__.py +0 -0
  298. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/twin/calibrate.py +0 -0
  299. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/twin/data/__init__.py +0 -0
  300. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/twin/data/position_effect.py +0 -0
  301. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/twin/mechanistic.py +0 -0
  302. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/twin/outcome.py +0 -0
  303. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/twin/position_effect.py +0 -0
  304. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/ui/__init__.py +0 -0
  305. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/ui/app.py +0 -0
  306. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/__init__.py +0 -0
  307. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/adapt_demo.py +0 -0
  308. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/agent_eval.py +0 -0
  309. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/bench_adversarial_tasks.py +0 -0
  310. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/bench_coscientist_tasks.py +0 -0
  311. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/bench_graph_tasks.py +0 -0
  312. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/bench_rule_tasks.py +0 -0
  313. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/bench_trust_tasks.py +0 -0
  314. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/bench_writetype_tasks.py +0 -0
  315. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/blind_gsh_discovery.py +0 -0
  316. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/cargo_directionality.py +0 -0
  317. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/closed_loop.py +0 -0
  318. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/durability_baselines.py +0 -0
  319. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/experiment_design.py +0 -0
  320. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/expr_controls.py +0 -0
  321. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/forward_hypotheses.py +0 -0
  322. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/generative_design.py +0 -0
  323. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/guide_qc_demo.py +0 -0
  324. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/heldout_celltype_expr.py +0 -0
  325. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/immune_calibration.py +0 -0
  326. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/intent_specification.py +0 -0
  327. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/known_biology_expr.py +0 -0
  328. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/offtarget_energetics_eval.py +0 -0
  329. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/out_of_scope_refusal.py +0 -0
  330. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/outcome_calibration.py +0 -0
  331. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/outcome_prediction.py +0 -0
  332. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/paper3_benchmark.py +0 -0
  333. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/paper4_real_validation.py +0 -0
  334. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/paper4_validation.py +0 -0
  335. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/protocol_safety.py +0 -0
  336. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/safety_screening.py +0 -0
  337. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/selective_prediction.py +0 -0
  338. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/seq_vs_measured.py +0 -0
  339. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/target_site_controls.py +0 -0
  340. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/uncertainty_eval.py +0 -0
  341. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/ungrounded_baseline.py +0 -0
  342. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/within_locus_ranking.py +0 -0
  343. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/validate/writer_recovery.py +0 -0
  344. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/verify/__init__.py +0 -0
  345. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/verify/proof.py +0 -0
  346. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/verify/schema.py +0 -0
  347. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/verify/service.py +0 -0
  348. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/web/__init__.py +0 -0
  349. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/web/guide.py +0 -0
  350. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/web/llm.py +0 -0
  351. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/web/router.py +0 -0
  352. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/web/server.py +0 -0
  353. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/web/tools.py +0 -0
  354. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/__init__.py +0 -0
  355. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/chromatin_seq.py +0 -0
  356. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/durability.py +0 -0
  357. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/export_tracks.py +0 -0
  358. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/features.py +0 -0
  359. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/gsh_baseline.py +0 -0
  360. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/mesh_features.py +0 -0
  361. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/offtarget_assay.py +0 -0
  362. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/offtarget_data.py +0 -0
  363. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/ood.py +0 -0
  364. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/providers.py +0 -0
  365. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/safety.py +0 -0
  366. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/structure3d.py +0 -0
  367. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/uncertainty.py +0 -0
  368. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack/wgenome/writability.py +0 -0
  369. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack.egg-info/dependency_links.txt +0 -0
  370. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack.egg-info/entry_points.txt +0 -0
  371. {pen_stack-6.13.0 → pen_stack-6.14.0}/pen_stack.egg-info/top_level.txt +0 -0
  372. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_phase0.json +0 -0
  373. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_phase1_5.json +0 -0
  374. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_phase2.json +0 -0
  375. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_phase3.json +0 -0
  376. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_a.json +0 -0
  377. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_acq.json +0 -0
  378. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_aldesign.json +0 -0
  379. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_alvalidate.json +0 -0
  380. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_atlas.json +0 -0
  381. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_b.json +0 -0
  382. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_ba.json +0 -0
  383. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_ba_v33.json +0 -0
  384. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_ba_v45.json +0 -0
  385. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_bench.json +0 -0
  386. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_c.json +0 -0
  387. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_cal.json +0 -0
  388. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_calib.json +0 -0
  389. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_challenge.json +0 -0
  390. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_chat.json +0 -0
  391. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_cite.json +0 -0
  392. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_continual.json +0 -0
  393. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_cosci2.json +0 -0
  394. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_crit.json +0 -0
  395. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_ct.json +0 -0
  396. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_d.json +0 -0
  397. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_delivery.json +0 -0
  398. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_drift.json +0 -0
  399. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_e.json +0 -0
  400. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_env.json +0 -0
  401. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_ep.json +0 -0
  402. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_epitope.json +0 -0
  403. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_expr2.json +0 -0
  404. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_f.json +0 -0
  405. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_frontend.json +0 -0
  406. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_g.json +0 -0
  407. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_gen.json +0 -0
  408. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_genotox.json +0 -0
  409. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_graph.json +0 -0
  410. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_h.json +0 -0
  411. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_hybrid.json +0 -0
  412. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_immune.json +0 -0
  413. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_immune2.json +0 -0
  414. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_ingest.json +0 -0
  415. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_innate.json +0 -0
  416. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_loop.json +0 -0
  417. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_manifest.json +0 -0
  418. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_mc.json +0 -0
  419. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_mcp.json +0 -0
  420. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_mech.json +0 -0
  421. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_mon.json +0 -0
  422. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_o.json +0 -0
  423. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_offtarget.json +0 -0
  424. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_openapi.json +0 -0
  425. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_oracle.json +0 -0
  426. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_orch.json +0 -0
  427. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_outcome.json +0 -0
  428. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_pareto.json +0 -0
  429. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_peg.json +0 -0
  430. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_plan.json +0 -0
  431. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_policy.json +0 -0
  432. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_profile.json +0 -0
  433. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_proto.json +0 -0
  434. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_r.json +0 -0
  435. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_redteam.json +0 -0
  436. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_route.json +0 -0
  437. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_screen.json +0 -0
  438. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_seroprev.json +0 -0
  439. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_simlab.json +0 -0
  440. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_twincal.json +0 -0
  441. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_uq.json +0 -0
  442. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_v.json +0 -0
  443. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_vcell.json +0 -0
  444. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_verify.json +0 -0
  445. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_writer.json +0 -0
  446. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/SHA256_LOCK_ws_wv.json +0 -0
  447. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/paper1.yaml +0 -0
  448. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/paper2.yaml +0 -0
  449. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/paper3.yaml +0 -0
  450. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/paper4.yaml +0 -0
  451. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/phase0.yaml +0 -0
  452. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_a.yaml +0 -0
  453. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_acq.yaml +0 -0
  454. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_aldesign.yaml +0 -0
  455. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_alvalidate.yaml +0 -0
  456. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_atlas.yaml +0 -0
  457. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_b.yaml +0 -0
  458. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_ba.yaml +0 -0
  459. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_ba_v33.yaml +0 -0
  460. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_ba_v45.yaml +0 -0
  461. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_bench.yaml +0 -0
  462. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_c.yaml +0 -0
  463. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_cal.yaml +0 -0
  464. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_calib.yaml +0 -0
  465. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_challenge.yaml +0 -0
  466. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_chat.yaml +0 -0
  467. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_cite.yaml +0 -0
  468. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_continual.yaml +0 -0
  469. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_cosci2.yaml +0 -0
  470. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_crit.yaml +0 -0
  471. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_ct.yaml +0 -0
  472. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_d.yaml +0 -0
  473. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_delivery.yaml +0 -0
  474. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_drift.yaml +0 -0
  475. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_e.yaml +0 -0
  476. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_env.yaml +0 -0
  477. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_ep.yaml +0 -0
  478. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_epitope.yaml +0 -0
  479. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_expr2.yaml +0 -0
  480. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_f.yaml +0 -0
  481. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_frontend.yaml +0 -0
  482. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_g.yaml +0 -0
  483. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_gen.yaml +0 -0
  484. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_genotox.yaml +0 -0
  485. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_graph.yaml +0 -0
  486. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_h.yaml +0 -0
  487. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_hybrid.yaml +0 -0
  488. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_immune.yaml +0 -0
  489. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_immune2.yaml +0 -0
  490. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_ingest.yaml +0 -0
  491. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_innate.yaml +0 -0
  492. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_loop.yaml +0 -0
  493. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_manifest.yaml +0 -0
  494. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_mc.yaml +0 -0
  495. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_mcp.yaml +0 -0
  496. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_mech.yaml +0 -0
  497. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_mon.yaml +0 -0
  498. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_o.yaml +0 -0
  499. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_offtarget.yaml +0 -0
  500. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_openapi.yaml +0 -0
  501. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_oracle.yaml +0 -0
  502. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_orch.yaml +0 -0
  503. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_outcome.yaml +0 -0
  504. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_pareto.yaml +0 -0
  505. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_peg.yaml +0 -0
  506. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_plan.yaml +0 -0
  507. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_policy.yaml +0 -0
  508. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_profile.yaml +0 -0
  509. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_proto.yaml +0 -0
  510. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_r.yaml +0 -0
  511. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_redteam.yaml +0 -0
  512. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_route.yaml +0 -0
  513. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_screen.yaml +0 -0
  514. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_seroprev.yaml +0 -0
  515. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_simlab.yaml +0 -0
  516. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_twincal.yaml +0 -0
  517. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_uq.yaml +0 -0
  518. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_v.yaml +0 -0
  519. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_vcell.yaml +0 -0
  520. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_verify.yaml +0 -0
  521. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_writer.yaml +0 -0
  522. {pen_stack-6.13.0 → pen_stack-6.14.0}/prereg/ws_wv.yaml +0 -0
  523. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/build_capsid_fitness.py +0 -0
  524. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/calibrate_immune_axes.py +0 -0
  525. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/fetch_licensed_sources.py +0 -0
  526. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/offtarget_chromatin_incremental.py +0 -0
  527. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/offtarget_chromatin_matched.py +0 -0
  528. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/offtarget_chromatin_validation.py +0 -0
  529. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_build_atlas.py +0 -0
  530. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_build_durability.py +0 -0
  531. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_build_position_effect.py +0 -0
  532. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_build_writer_eff.py +0 -0
  533. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_export_tracks.py +0 -0
  534. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_safety_concordance.py +0 -0
  535. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_train_safety.py +0 -0
  536. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p1_validation_report.py +0 -0
  537. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p2_build_atlas.py +0 -0
  538. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p3_benchmark_report.py +0 -0
  539. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p4_genome_scan.py +0 -0
  540. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p52_build_genotox_oracle.py +0 -0
  541. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/p53_build_epitope_oracle.py +0 -0
  542. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/ws_b_report.py +0 -0
  543. {pen_stack-6.13.0 → pen_stack-6.14.0}/scripts/ws_c_report.py +0 -0
  544. {pen_stack-6.13.0 → pen_stack-6.14.0}/setup.cfg +0 -0
@@ -3,6 +3,40 @@
3
3
  All notable changes to PEN-STACK are documented here. This file follows
4
4
  [Keep a Changelog](https://keepachangelog.com/).
5
5
 
6
+ ## [6.14.0] - 2026-06-22 - WriteSpec: a typed, ontology-backed intent layer with a grounded extractor and a feasibility check
7
+
8
+ Replaces the keyword parser with a typed, machine-checkable genome-writing request (an SBOL3 profile), a grounded
9
+ prose-to-spec extractor that labels every inference and never fabricates intent, and a feasibility check. This is
10
+ the agentic front-end: one contract the whole stack consumes. A WriteSpec is a request, not a claim.
11
+
12
+ ### Added
13
+ - **The WriteRequest type** (`pen_stack/spec/writespec.py`, `schemas/writespec.json`). A typed model carrying the
14
+ write semantics (write-type, cargo with Sequence-Ontology roles, target gene/locus/att-site/phenotype, cell
15
+ type, constraints) plus a per-field provenance map (explicit / inferred / user / unresolved). Lossless JSON
16
+ round-trip; SBOL3 round-trip via the real `sbol3` library (the `[spec]` extra; native Components + SO roles for
17
+ interoperability); GenBank export for a cargo with a sequence; and `to_legacy_design`, the adapter that lets
18
+ every existing stage consume a WriteRequest without a rewrite.
19
+ - **Vocabulary resolvers** (`pen_stack/spec/resolvers/`). Free text to canonical id: HGNC genes (atlas-grounded),
20
+ Cellosaurus / Cell Ontology cell types, Sequence Ontology feature roles, MONDO phenotypes, ChEBI molecules,
21
+ GRCh38 coordinates. Every curated id was verified against the live ontology services before commit; an
22
+ unresolved term stays null, never invented.
23
+ - **Grounded extractor + clarifying-question planner** (`pen_stack/spec/extract.py`, `clarify.py`). A
24
+ deterministic backbone (so the benchmark is reproducible) that labels every inferred field, asks a clarifying
25
+ question for anything underspecified or ambiguous, and keeps unresolved terms null.
26
+ - **Feasibility check** (`pen_stack/spec/satisfy.py`). Wraps reachability (the atlas), deliverability (the
27
+ delivery recommender), and legality (the verification proof object) into feasible / infeasible plus named
28
+ blocking constraints and repair hints; feasibility is necessary, not sufficient (not efficacy).
29
+ - **WriteSpec-Bench** (`benchmarks/writespec/`). The first prose-to-write-spec corpus, grounded in real
30
+ experiments, with an ambiguity subset and a sealed held-out. Reported verbatim: sealed-test structural fidelity
31
+ 1.0 and value accuracy 0.96 versus a 0.46 keyword-dict baseline; inferred-field labelling recall 1.0.
32
+ - **Surfaces**: REST `POST /api/writespec`, MCP `writespec_parse`, the manifest tool, the web "Describe a Write"
33
+ builder page, and `docs/writespec_profile.md` + `docs/writespec_bench.md`.
34
+
35
+ ### Notes
36
+ - Feasibility rules out unreachable / undeliverable / illegal, not whether the write will work.
37
+ - The extractor backbone is deterministic; an LLM pass is optional and adds no ground truth (it may only propose
38
+ values that still pass the resolvers).
39
+
6
40
  ## [6.13.0] - 2026-06-22 - Oracle mesh: binding-affinity dimension, per-oracle reliability, disagreement-to-uncertainty
7
41
 
8
42
  Hardens the foundation-model oracle mesh under one result contract. Adds a binding-affinity dimension
@@ -1,7 +1,7 @@
1
1
  cff-version: 1.2.0
2
2
  message: "If you use PEN-STACK, please cite it as below."
3
3
  title: "PEN-STACK: open infrastructure for genome writing"
4
- version: 6.13.0
4
+ version: 6.14.0
5
5
  date-released: 2026-06-20
6
6
  authors:
7
7
  - family-names: "Mahaboob Ali"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pen-stack
3
- Version: 6.13.0
3
+ Version: 6.14.0
4
4
  Summary: Open infrastructure for genome writing: the Writable Genome atlas, the Writer Atlas, and the Write Planner.
5
5
  Author-email: Anees Ahmed Mahaboob Ali <ahmedaneesm@gmail.com>
6
6
  License: MIT
@@ -67,6 +67,8 @@ Requires-Dist: mkdocs>=1.6; extra == "docs"
67
67
  Requires-Dist: mkdocs-material>=9.5; extra == "docs"
68
68
  Provides-Extra: env
69
69
  Requires-Dist: gymnasium>=0.29; extra == "env"
70
+ Provides-Extra: spec
71
+ Requires-Dist: sbol3>=1.1; extra == "spec"
70
72
  Provides-Extra: dev
71
73
  Requires-Dist: pytest>=8; extra == "dev"
72
74
  Requires-Dist: ruff>=0.5; extra == "dev"
@@ -0,0 +1,4 @@
1
+ 5bbfd8b7d73c0828a72d84d68b12155f7047e45d0d227e8c9654cc15687991f5 benchmarks/writespec/corpus.json
2
+ d3133084f098a6eeed46ab49555731057c9b106c787f090331df6b019cd249f2 benchmarks/writespec/harness.py
3
+ bc6126634628e92baedf68404636889f07c77c53d59ca057b18b7f54511a11fe benchmarks/writespec/writespec_bench_metrics.json
4
+ 051ddbaccca5faab72d66f2737e634b5b7170aa919323aab73f98e53fd042b36 benchmarks/writespec/splits.json
@@ -20,8 +20,10 @@ A nominated off-target is a candidate, and every result ships with the empirical
20
20
  controls) found that with a **cell-type-matched** track (ENCODE HEK293T DNase, v6.10.3) accessibility predicts
21
21
  WT-Cas9 cell-based off-target activity: **GUIDE-seq AUROC 0.58 (cross-cell K562) → 0.671 (matched), CI
22
22
  [0.642, 0.701]**, in-vitro control null. **VALIDATED (moderate, cell-type-matched)**; it is surfaced as an
23
- annotation and does **not yet change the numeric risk score** (sequence/CRISOT dominates; TTISS, a Cas9-variant
24
- assay, is the expected outlier). Full result: `benchmarks/offtarget/chromatin_validation.json`.
23
+ annotation and is **intentionally not folded into the numeric risk score** (the v6.10.4 incremental analysis
24
+ found no held-out ranking improvement over CRISOT, so the score stays sequence/CRISOT-driven; TTISS, a
25
+ Cas9-variant assay, is the expected outlier). Full results:
26
+ `benchmarks/offtarget/chromatin_{validation,incremental}.json`.
25
27
  - **Serine integrase (Bxb1):** a cryptic **pseudo-attB** scan that seeds on the *real documented* Bxb1 attB core
26
28
  (`GCGGTCTC`, central GT; FlyBase FBto0000359, Ghosh 2003) and reports candidate cryptic sites by arm mismatches.
27
29
  - **Bridge recombinase:** delegates to the existing Perry-DMS pseudosite engine (`pen_stack.bridge.offtarget`).
@@ -0,0 +1,44 @@
1
+ # WriteSpec-Bench (Stage A)
2
+
3
+ WriteSpec-Bench is the first prose-to-write-spec benchmark for genome writing: paired
4
+ `(plain-language request -> gold WriteSpec)`, grounded in real, well-documented genome-writing experiments
5
+ (cited per row). It is `benchmarks/writespec/` (corpus, splits, harness, metrics).
6
+
7
+ ## Construction
8
+
9
+ - Each gold WriteSpec is the structured value a careful curator extracts: write-type, target kind, the gene
10
+ (HGNC) or phenotype (MONDO) id, the cell (Cellosaurus / Cell Ontology) id, the cargo Sequence-Ontology role
11
+ set, and the key constraints. Every ontology id was verified against the live services before commit.
12
+ - The **standard** subset is fully specified. The **ambiguity** subset is deliberately underspecified or
13
+ ambiguous, to test that the extractor surfaces assumptions and asks clarifying questions rather than guessing.
14
+ - **Leakage control**: the test split is sealed and disjoint from train (`splits.json`); paraphrases do not
15
+ co-occur across splits.
16
+
17
+ ## Scoring (SO-Bench-style)
18
+
19
+ - **schema adherence**: the output validates as a typed `WriteRequest`.
20
+ - **structural fidelity**: write_type and target_kind match the gold.
21
+ - **value accuracy**: canonical-id match for gene / phenotype / cell, the cargo role set, and the listed
22
+ constraints.
23
+ - **grounding**: on the ambiguity subset, clarifying questions fire and the inferred write_type is labelled;
24
+ inferred-field labelling recall must be 100% (no field is set without provenance).
25
+
26
+ The baseline is the legacy keyword dictionary (`web.tools.parse_goal`), which emits raw tokens, not canonical
27
+ ids.
28
+
29
+ ## Results (reported verbatim, deterministic extractor, not tuned to the sealed test)
30
+
31
+ - Sealed test: schema adherence 1.0, structural fidelity 1.0, value accuracy 0.964; baseline value accuracy
32
+ 0.464.
33
+ - Train: structural fidelity 1.0, value accuracy 0.988; baseline 0.407.
34
+ - Ambiguity subset: clarifying questions fire, inferred write_type labelled.
35
+ - Inferred-field labelling recall: 1.0.
36
+
37
+ The extractor beats the keyword-dict baseline because it resolves terms to verified ontology ids; the baseline
38
+ keeps raw tokens. The corpus is small and curated, grounded in real experiments; its value is bounded by curation
39
+ quality, reported verbatim. Two structural mismatches found during development were genuine extractor bugs (a
40
+ cell-line token mis-read as a gene; a disease prioritized over a named gene target), fixed in the extractor, not
41
+ papered over in the gold.
42
+
43
+ The full per-run metrics are in `benchmarks/writespec/writespec_bench_metrics.json`; re-run with
44
+ `python -m benchmarks.writespec.harness`.
@@ -0,0 +1,58 @@
1
+ # The WriteSpec profile (Stage A)
2
+
3
+ WriteSpec is a typed, ontology-backed, machine-checkable representation of a genome-writing request. It replaces
4
+ the keyword parser with one contract every downstream stage consumes, and it is an SBOL3 profile, so it is
5
+ interoperable with the genetic-design-automation ecosystem and GenBank from day one. A WriteSpec is a request,
6
+ not a claim.
7
+
8
+ ## The type
9
+
10
+ `WriteRequest` (`pen_stack/spec/writespec.py`) carries:
11
+
12
+ - **write_type**: one of insertion, excision, inversion, replacement, regulatory_rewrite, landing_pad_install,
13
+ multiplex.
14
+ - **cargo**: a list of components, each with a Sequence-Ontology role (promoter `SO:0000167`, CDS `SO:0000316`,
15
+ polyA signal `SO:0000551`, insulator `SO:0000627`, ...) and an optional sequence and length.
16
+ - **target**: the edit site, as a gene (HGNC), a locus (GRCh38 coordinate), an att/landing site, or, when only a
17
+ disease goal is given, a phenotype (MONDO). A named gene or att site is the target; a disease is attached as the
18
+ goal.
19
+ - **cell_type**: a Cellosaurus cell line (HEK293T `CVCL_0063`) or a Cell-Ontology cell type (T cell `CL:0000084`),
20
+ carrying the irreducible subline / karyotype-drift caveat.
21
+ - **constraints**: efficiency floor, scarless, safety-switch-required, copy number, germline guardrail, max cargo
22
+ bp, delivery limit / vehicle, and a ChEBI small-molecule inducer.
23
+
24
+ ## The grounding discipline
25
+
26
+ Every field records its provenance in the `provenance` map: `explicit` (stated in the prose), `inferred`
27
+ (defaulted, with the rationale in `assumptions`), `user` (supplied through a structured override), or
28
+ `unresolved`. A required field that is unspecified or ambiguous yields a `clarifications` question rather than a
29
+ guess. A term that cannot be resolved is listed in `unresolved` and its field stays null. Nothing is invented.
30
+
31
+ All curated ontology ids were verified against the live services (Cellosaurus, EBI OLS for SO / MONDO / CL /
32
+ ChEBI) before they were committed; a resolver degrades to `unresolved`, never a fabricated id.
33
+
34
+ ## Round-trips and the downstream adapter
35
+
36
+ - **JSON**: always available (`to_json` / `from_json`), lossless.
37
+ - **SBOL3**: `to_sbol3` / `from_sbol3` via the real `sbol3` library (the `[spec]` extra). The export emits native
38
+ Components with Sequence-Ontology roles for interoperability and carries the full typed spec losslessly in a
39
+ PROV-O-namespaced annotation.
40
+ - **GenBank**: `to_genbank` exports the cargo as a GenBank record when it carries a DNA sequence (intent-only
41
+ specs return None).
42
+ - **`to_legacy_design`**: the adapter that emits the dict the existing stages (verify / plan / safety / delivery)
43
+ already consume, so the whole stack reads one contract without a per-stage rewrite.
44
+
45
+ ## Feasibility
46
+
47
+ `pen_stack/spec/satisfy.py` tests the necessary conditions a write must meet by wrapping the existing stages:
48
+ reachability (the writable-genome atlas), deliverability (the Stage D recommender), and legality (the Stage F
49
+ rule set, via the repair-oriented proof object). It returns feasible or infeasible plus the named blocking
50
+ constraint(s) and repair hints, so an agent can repair the spec and re-check. Feasibility is necessary, not
51
+ sufficient: it rules out unreachable / undeliverable / illegal, not whether the write will work (that is the
52
+ downstream stages' calibrated prediction).
53
+
54
+ ## Surfaces
55
+
56
+ - REST `POST /api/writespec` (body `{prose, overrides?, check_feasibility?}`).
57
+ - MCP `writespec_parse`.
58
+ - The web "Describe a Write" builder page.
@@ -1,2 +1,2 @@
1
1
  """PEN-STACK v3.0 - open infrastructure for genome writing."""
2
- __version__ = "6.13.0"
2
+ __version__ = "6.14.0"
@@ -60,6 +60,18 @@ def verify_proof(design: dict) -> dict:
60
60
  return _vp(design).model_dump()
61
61
 
62
62
 
63
+ @mcp.tool()
64
+ def writespec_parse(prose: str, check_feasibility: bool = True) -> dict:
65
+ """v6.14 Stage A: parse a plain-language genome-writing request into a typed, ontology-backed WriteSpec (an
66
+ SBOL3 profile). Returns the typed spec with per-field provenance (explicit / inferred / user / unresolved),
67
+ the assumptions behind every inferred field, clarifying questions for anything underspecified or ambiguous,
68
+ the unresolved terms (kept null, never invented), the downstream design adapter, and a feasibility verdict
69
+ (reachability + deliverability + legality, with named blocking constraints + repair hints). A WriteSpec is a
70
+ REQUEST, not a claim; the extractor never fabricates intent."""
71
+ from pen_stack.spec.service import parse_request
72
+ return parse_request(prose, check_feasibility=check_feasibility)
73
+
74
+
63
75
  @mcp.tool()
64
76
  def oracle_query(oracle: str | None = None, protein_seq: str | None = None, ligand_smiles: str | None = None,
65
77
  pair_type: str = "ligand", ligand_name: str | None = None) -> dict:
@@ -67,6 +67,12 @@ _TOOLS = [
67
67
  "NOT an in-vivo tropism claim (v6.11 PEN-DELIVER)",
68
68
  "input": "AAV VP1 capsid amino-acid sequence", "output": "predicted packaging-fitness (candidate) or abstain",
69
69
  "entrypoint": "pen_stack.planner.delivery_predict.capsid_fitness", "fabricates": False},
70
+ {"name": "writespec_parse", "summary": "parse a plain-language genome-writing request into a typed, "
71
+ "ontology-backed WriteSpec (an SBOL3 profile): per-field provenance (explicit/inferred/user/unresolved), "
72
+ "assumptions, clarifying questions on underspecification, and a feasibility verdict (reachability + "
73
+ "deliverability + legality); a REQUEST not a claim, never fabricates intent (v6.14 Stage A WriteSpec)",
74
+ "input": "prose request (+ optional structured overrides)", "output": "typed WriteSpec + clarifications + "
75
+ "feasibility; unresolved stays null", "entrypoint": "pen_stack.spec.service.parse_request", "fabricates": False},
70
76
  {"name": "oracle_query", "summary": "query the oracle mesh under one contract: per-oracle execution + latency "
71
77
  "+ live status + PUBLISHED reliability (verbatim from public benchmarks, cited) + disagreement-to-interval; "
72
78
  "or a CANDIDATE protein-ligand binding affinity (Boltz-2 head) with native uncertainty, cache-or-abstain "
@@ -238,6 +238,18 @@ def offtarget_assay_endpoint(writer_family: str):
238
238
  return recommend_assay(writer_family)
239
239
 
240
240
 
241
+ @app.post("/writespec", tags=["v6.14 writespec"])
242
+ def writespec_endpoint(req: dict):
243
+ """v6.14 Stage A: parse a plain-language genome-writing request into a typed, ontology-backed WriteSpec.
244
+ Body: {prose, overrides?, check_feasibility?}. Returns the typed spec (with per-field provenance), the
245
+ assumptions behind every inferred field, clarifying questions for anything underspecified, the unresolved
246
+ terms (kept null, never invented), the downstream design adapter, and the feasibility verdict. A WriteSpec is
247
+ a REQUEST, not a claim; the extractor never fabricates intent."""
248
+ from pen_stack.spec.service import parse_request
249
+ return parse_request(req.get("prose", ""), overrides=req.get("overrides"),
250
+ check_feasibility=bool(req.get("check_feasibility", True)))
251
+
252
+
241
253
  @app.post("/oracle/affinity", tags=["v6.13 oracle"])
242
254
  def oracle_affinity_endpoint(req: dict):
243
255
  """v6.13 PEN-ORACLE protein-ligand binding-affinity (Boltz-2 head) under the oracle contract. Body:
@@ -0,0 +1,18 @@
1
+ """The WriteSpec intent layer (v6.14, Stage A).
2
+
3
+ A typed, ontology-backed, machine-checkable representation of a genome-writing request (``WriteRequest``, an
4
+ SBOL3 profile), a grounded prose-to-spec extractor that labels every inference and never fabricates intent, and a
5
+ feasibility (satisfiability) check. This is the agentic front-end: one contract every downstream stage consumes.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from pen_stack.spec.writespec import (
10
+ CargoComponent,
11
+ Constraints,
12
+ Resolved,
13
+ Target,
14
+ WriteRequest,
15
+ WRITE_TYPES,
16
+ )
17
+
18
+ __all__ = ["WriteRequest", "CargoComponent", "Target", "Constraints", "Resolved", "WRITE_TYPES"]
@@ -0,0 +1,42 @@
1
+ """Clarifying-question planner (v6.14, Stage A, A-WS3).
2
+
3
+ Given a :class:`WriteRequest`, return the minimal set of questions needed to make it actionable: the required
4
+ fields that are unspecified or ambiguous. The extractor already populates ``clarifications`` during parsing;
5
+ this module is the standalone planner an agent calls to decide whether to ask before composing a write, and to
6
+ get a clean, de-duplicated question list. It never guesses a value: a missing required field becomes a question.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pen_stack.spec.writespec import WriteRequest
11
+
12
+ # fields that must be present for a write to be composable (by write-type)
13
+ _REQUIRED_TARGET = {"insertion", "excision", "inversion", "replacement", "regulatory_rewrite",
14
+ "landing_pad_install", "multiplex"}
15
+
16
+
17
+ def clarifying_questions(spec: WriteRequest) -> list[str]:
18
+ """The minimal de-duplicated question list for the underspecified / ambiguous required fields."""
19
+ qs: list[str] = list(spec.clarifications) # carry the extractor's questions
20
+ if spec.write_type in _REQUIRED_TARGET and spec.target.kind == "unspecified":
21
+ q = "Which gene, locus, att/landing site, or disease phenotype should the write target?"
22
+ if q not in qs:
23
+ qs.append(q)
24
+ # an insertion with no cargo at all is underspecified
25
+ if spec.write_type == "insertion" and not spec.cargo:
26
+ qs.append("What cargo should be inserted (size in bp/kb, and any features such as promoter / CDS / polyA)?")
27
+ # an unresolved term needs disambiguation, not a guess
28
+ for term in spec.unresolved:
29
+ qs.append(f"The term '{term}' could not be resolved to a gene / locus / cell / phenotype; please clarify it.")
30
+ # de-duplicate, preserve order
31
+ seen: set[str] = set()
32
+ out: list[str] = []
33
+ for q in qs:
34
+ if q not in seen:
35
+ seen.add(q)
36
+ out.append(q)
37
+ return out
38
+
39
+
40
+ def is_actionable(spec: WriteRequest) -> bool:
41
+ """True when no clarifying question remains (the spec is complete enough to compose a write)."""
42
+ return not clarifying_questions(spec)
@@ -0,0 +1,232 @@
1
+ """Grounded prose-to-WriteSpec extractor (v6.14, Stage A, A-WS3).
2
+
3
+ Maps a plain-language genome-writing request to a typed :class:`WriteRequest`. The backbone is DETERMINISTIC (so
4
+ the benchmark is reproducible and CI-safe); an LLM pass is optional and never required. Three grounding
5
+ safeguards are mandatory and encoded here:
6
+
7
+ 1. assumption surfacing - every field not explicit in the prose is recorded in ``provenance`` as ``inferred``
8
+ with the rationale in ``assumptions`` (never a silent default);
9
+ 2. clarifying questions - a required field that is unspecified or ambiguous yields a ``clarifications``
10
+ question rather than a guess;
11
+ 3. no fabrication - a term that cannot be resolved is listed in ``unresolved`` and its field stays null.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import re
16
+
17
+ from pen_stack.spec.resolvers import (
18
+ resolve_cell,
19
+ resolve_chem,
20
+ resolve_feature,
21
+ resolve_gene,
22
+ resolve_locus,
23
+ resolve_phenotype,
24
+ )
25
+ from pen_stack.spec.resolvers.phenotype import _PHENO
26
+ from pen_stack.spec.writespec import (
27
+ CargoComponent,
28
+ Constraints,
29
+ Target,
30
+ WriteRequest,
31
+ )
32
+
33
+ _GENE_RE = re.compile(r"\b([A-Z][A-Z0-9]{1,7})\b")
34
+ _WRITE_TYPE_KW = [
35
+ ("excision", ["excise", "delete", "knock out", "knockout", "remove ", "disrupt"]),
36
+ ("inversion", ["invert", "inversion", "flip "]),
37
+ ("replacement", ["replace", "correct the", "correct a", "swap", "base edit", "prime edit", "repair the"]),
38
+ ("regulatory_rewrite", ["regulatory", "promoter swap", "rewrite the promoter", "tune expression", "upregulate", "knock down"]),
39
+ ("landing_pad_install", ["landing pad", "landing-pad", "install an att", "install a landing", "attp", "bxb1 site"]),
40
+ ("multiplex", ["multiplex", "multiple loci", "simultaneously edit"]),
41
+ ("insertion", ["insert", "integrate", "knock in", "knock-in", "knockin", "add a", "deliver a transgene", "place a"]),
42
+ ]
43
+ _ROLE_KW = {"promoter": "promoter", "cds": "CDS", "coding": "CDS", "transgene": "CDS", "gfp": "CDS",
44
+ "polya": "polyA", "poly-a": "polyA", "insulator": "insulator", "enhancer": "enhancer",
45
+ "terminator": "terminator", "ires": "IRES"}
46
+
47
+
48
+ def _detect_write_type(low: str) -> tuple[str, bool]:
49
+ for wt, kws in _WRITE_TYPE_KW:
50
+ if any(k in low for k in kws):
51
+ return wt, True
52
+ return "insertion", False # default, labelled inferred by the caller
53
+
54
+
55
+ def _gene_tokens(prose: str) -> list[str]:
56
+ """Gene-symbol tokens, excluding jargon and any token that resolves as a cell line / type (e.g. HEK293T)."""
57
+ from pen_stack.spec.resolvers.cell import _CELLS
58
+ from pen_stack.spec.resolvers.gene import _STOP
59
+ cell_keys = {k.upper() for k in _CELLS}
60
+ return [g for g in _GENE_RE.findall(prose) if g not in _STOP and g.upper() not in cell_keys]
61
+
62
+
63
+ def _detect_bp(low: str) -> int | None:
64
+ m = re.search(r"(\d+(?:\.\d+)?)\s*kb", low)
65
+ if m:
66
+ return int(float(m.group(1)) * 1000)
67
+ m = re.search(r"(\d{2,6})\s*(?:bp|base pairs)", low)
68
+ if m:
69
+ return int(m.group(1))
70
+ return None
71
+
72
+
73
+ def _detect_phenotype(prose: str):
74
+ low = prose.lower()
75
+ for phrase in sorted(_PHENO, key=len, reverse=True):
76
+ if phrase in low:
77
+ return resolve_phenotype(phrase)
78
+ return None
79
+
80
+
81
+ def _detect_cell(prose: str):
82
+ from pen_stack.spec.resolvers.cell import _CELLS
83
+ low = prose.lower()
84
+ for phrase in sorted(_CELLS, key=len, reverse=True):
85
+ if phrase in low:
86
+ return resolve_cell(phrase)
87
+ return None
88
+
89
+
90
+ def _detect_inducer(prose: str):
91
+ from pen_stack.spec.resolvers.chem import _CHEM
92
+ low = prose.lower()
93
+ for phrase in sorted(_CHEM, key=len, reverse=True):
94
+ if phrase in low:
95
+ return resolve_chem(phrase)
96
+ return None
97
+
98
+
99
+ def extract_writespec(prose: str, *, overrides: dict | None = None, allow_llm: bool = False) -> WriteRequest:
100
+ """Extract a typed WriteRequest from prose, deterministically, with grounding safeguards.
101
+
102
+ ``overrides`` lets a caller (or the web builder) supply a field directly; those are marked provenance ``user``.
103
+ ``allow_llm`` is accepted for surface parity but the deterministic backbone is what runs and is benchmarked;
104
+ an LLM pass, when wired, may only PROPOSE values that still pass the resolvers (it can never set an
105
+ unresolved id).
106
+ """
107
+ prose = prose or ""
108
+ low = prose.lower()
109
+ prov: dict[str, str] = {}
110
+ assumptions: list[str] = []
111
+ unresolved: list[str] = []
112
+ clarifications: list[str] = []
113
+ overrides = overrides or {}
114
+
115
+ # --- write type ---
116
+ if "write_type" in overrides:
117
+ wt = overrides["write_type"]
118
+ prov["write_type"] = "user"
119
+ else:
120
+ wt, explicit = _detect_write_type(low)
121
+ prov["write_type"] = "explicit" if explicit else "inferred"
122
+ if not explicit:
123
+ assumptions.append("write_type inferred as 'insertion' (no explicit write verb; the default for a "
124
+ "cargo-placement request)")
125
+
126
+ # --- target: a named gene/locus or att site is the EDIT SITE (target_kind); a disease is the GOAL ---
127
+ target = Target()
128
+ pheno = _detect_phenotype(prose)
129
+ gene_toks = _gene_tokens(prose)
130
+ att = None
131
+ for k in ("attb", "attp", "landing pad", "landing-pad", "landing site"):
132
+ if k in low:
133
+ att = k.replace(" ", "_").replace("-", "_")
134
+ break
135
+ gene_resolved = None
136
+ if gene_toks:
137
+ g = resolve_gene(gene_toks[0])
138
+ if g.resolved:
139
+ gene_resolved = g
140
+ else:
141
+ unresolved.append(gene_toks[0])
142
+ if gene_resolved is not None:
143
+ loc = resolve_locus(gene_toks[0])
144
+ target = Target(kind="gene", gene=gene_resolved, locus=loc if loc.resolved else None)
145
+ prov["target.gene"] = "explicit"
146
+ if pheno is not None and pheno.resolved: # the disease is the goal, attached to the gene target
147
+ target.phenotype = pheno
148
+ prov["target.phenotype"] = "explicit"
149
+ elif att:
150
+ target = Target(kind="att_site", att_site=att)
151
+ prov["target.att_site"] = "explicit"
152
+ if pheno is not None and pheno.resolved:
153
+ target.phenotype = pheno
154
+ prov["target.phenotype"] = "explicit"
155
+ elif pheno is not None and pheno.resolved:
156
+ target = Target(kind="phenotype", phenotype=pheno)
157
+ prov["target.phenotype"] = "explicit"
158
+ if target.kind == "unspecified":
159
+ clarifications.append("Which gene, locus, att/landing site, or disease phenotype should the write target?")
160
+
161
+ # --- cell type (optional but recommended) ---
162
+ cell = None
163
+ ct = _detect_cell(prose)
164
+ if ct is not None and ct.resolved:
165
+ cell = ct
166
+ prov["cell_type"] = "explicit"
167
+ elif ct is not None and ct.candidates:
168
+ cell = ct
169
+ prov["cell_type"] = "explicit"
170
+ clarifications.append(f"Cell term '{ct.text}' is ambiguous: did you mean "
171
+ f"{', '.join(c['label'] for c in ct.candidates)}?")
172
+ else:
173
+ clarifications.append("Which cell type or cell line is the target (e.g. HEK293T, primary T cells, HSPCs)?")
174
+
175
+ # --- cargo (size + feature roles) ---
176
+ cargo: list[CargoComponent] = []
177
+ bp = _detect_bp(low)
178
+ seen_roles: set[str] = set()
179
+ for kw, label in _ROLE_KW.items():
180
+ if kw in low and label not in seen_roles:
181
+ seen_roles.add(label)
182
+ role = resolve_feature(label)
183
+ cargo.append(CargoComponent(name=kw, role=role if role.resolved else None))
184
+ prov[f"cargo[{len(cargo) - 1}].role"] = "explicit"
185
+ if bp is not None and cargo:
186
+ cargo[0].length_bp = bp
187
+ elif bp is not None:
188
+ cargo.append(CargoComponent(name="cargo", length_bp=bp))
189
+
190
+ # --- constraints ---
191
+ cons = Constraints()
192
+ if "scarless" in low or "seamless" in low:
193
+ cons.scarless = True
194
+ prov["constraints.scarless"] = "explicit"
195
+ if "safety switch" in low or "kill switch" in low or "icasp9" in low or "suicide gene" in low:
196
+ cons.safety_switch_required = True
197
+ prov["constraints.safety_switch_required"] = "explicit"
198
+ # named delivery vehicle (so the legality / capacity check can run) - most specific first
199
+ _VEH = [("dual aav", "AAV_dual"), ("aav", "AAV_single"), ("lentivir", "lentivirus"), ("lnp", "LNP"),
200
+ ("electroporat", "electroporation"), ("rnp", "electroporation"), ("adenovir", "adenovirus")]
201
+ for kw, veh in _VEH:
202
+ if kw in low:
203
+ cons.delivery_limit = veh
204
+ prov["constraints.delivery_limit"] = "explicit"
205
+ break
206
+ if cons.delivery_limit is None and ("non-integrating" in low or "nonintegrating" in low
207
+ or "episomal" in low or "transient" in low):
208
+ cons.delivery_limit = "non_integrating"
209
+ prov["constraints.delivery_limit"] = "explicit"
210
+ if "germline" in low or "embryo" in low or "heritable" in low:
211
+ cons.germline = True
212
+ prov["constraints.germline"] = "explicit"
213
+ m = re.search(r"(?:at least|>=|over|above)\s*(\d{1,3})\s*%\s*(?:efficien|edit|integrat)", low)
214
+ if m:
215
+ cons.efficiency_floor = float(m.group(1)) / 100.0
216
+ prov["constraints.efficiency_floor"] = "explicit"
217
+ inducer = _detect_inducer(prose)
218
+ if inducer is not None and inducer.resolved:
219
+ cons.inducer = inducer
220
+ prov["constraints.inducer"] = "explicit"
221
+ if bp is not None:
222
+ cons.max_cargo_bp = bp
223
+ prov["constraints.max_cargo_bp"] = "explicit"
224
+
225
+ # apply structured overrides (provenance=user) last
226
+ for k, v in overrides.items():
227
+ if k != "write_type":
228
+ prov[k] = "user"
229
+
230
+ return WriteRequest(write_type=wt, cargo=cargo, target=target, cell_type=cell, constraints=cons,
231
+ provenance=prov, assumptions=assumptions, clarifications=clarifications,
232
+ unresolved=unresolved, free_text_note=prose.strip() or None)
@@ -0,0 +1,17 @@
1
+ """Vocabulary resolvers (v6.14, Stage A, A-WS2): free text -> canonical ontology id.
2
+
3
+ Each resolver returns a :class:`pen_stack.spec.writespec.Resolved` with a canonical id + confidence + (when
4
+ ambiguous) a ranked candidate set, or a result with ``id=None`` for an unresolved term. Unresolved stays null:
5
+ a resolver never invents an id. All curated ids were verified against the live ontology services (Cellosaurus,
6
+ EBI OLS for SO / MONDO / CL / ChEBI) before they were committed.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pen_stack.spec.resolvers.cell import resolve_cell
11
+ from pen_stack.spec.resolvers.chem import resolve_chem
12
+ from pen_stack.spec.resolvers.feature import resolve_feature
13
+ from pen_stack.spec.resolvers.gene import resolve_gene
14
+ from pen_stack.spec.resolvers.locus import resolve_locus
15
+ from pen_stack.spec.resolvers.phenotype import resolve_phenotype
16
+
17
+ __all__ = ["resolve_gene", "resolve_cell", "resolve_feature", "resolve_phenotype", "resolve_chem", "resolve_locus"]
@@ -0,0 +1,51 @@
1
+ """Cell-line / cell-type resolver (A-WS2): free text -> Cellosaurus (cell lines) or Cell Ontology (cell types).
2
+
3
+ Curated offline cache (ids verified against the Cellosaurus API and EBI OLS). Cell lines carry the irreducible
4
+ subline / karyotype-drift caveat as a note. An unrecognized term stays unresolved (id=None), never invented.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from pen_stack.spec.writespec import Resolved
9
+
10
+ # (id, canonical label, ontology) - all ids verified live before commit
11
+ _CELLS: dict[str, tuple[str, str, str]] = {
12
+ # cell lines (Cellosaurus)
13
+ "hek293t": ("CVCL_0063", "HEK293T", "Cellosaurus"), "293t": ("CVCL_0063", "HEK293T", "Cellosaurus"),
14
+ "k562": ("CVCL_0004", "K-562", "Cellosaurus"), "k-562": ("CVCL_0004", "K-562", "Cellosaurus"),
15
+ "hepg2": ("CVCL_0027", "Hep-G2", "Cellosaurus"), "hep-g2": ("CVCL_0027", "Hep-G2", "Cellosaurus"),
16
+ "jurkat": ("CVCL_0065", "Jurkat", "Cellosaurus"),
17
+ "hela": ("CVCL_0030", "HeLa", "Cellosaurus"),
18
+ # cell types (Cell Ontology)
19
+ "t cell": ("CL:0000084", "T cell", "CL"), "t-cell": ("CL:0000084", "T cell", "CL"),
20
+ "cd8 t cell": ("CL:0000084", "T cell", "CL"), "car-t": ("CL:0000084", "T cell", "CL"),
21
+ "primary t cell": ("CL:0000084", "T cell", "CL"),
22
+ "hspc": ("CL:0000037", "hematopoietic stem cell", "CL"), "hsc": ("CL:0000037", "hematopoietic stem cell", "CL"),
23
+ "cd34+": ("CL:0000037", "hematopoietic stem cell", "CL"), "cd34": ("CL:0000037", "hematopoietic stem cell", "CL"),
24
+ "neuron": ("CL:0000540", "neuron", "CL"),
25
+ }
26
+ _LINE_CAVEAT = ("cell-line identity carries irreducible subline / karyotype drift (e.g. HEK293T); the canonical "
27
+ "id does not resolve that away")
28
+
29
+
30
+ def resolve_cell(text: str) -> Resolved:
31
+ """Resolve a cell line / type to Cellosaurus or Cell Ontology; unrecognized stays null."""
32
+ if not text:
33
+ return Resolved(text=text, note="empty")
34
+ key = text.strip().lower()
35
+ hit = _CELLS.get(key)
36
+ if hit is None:
37
+ # try a loose contains-match for a ranked candidate set (never a silent pick)
38
+ cands = [{"id": v[0], "label": v[1], "ontology": v[2]}
39
+ for k, v in _CELLS.items() if k in key or key in k]
40
+ uniq = {c["id"]: c for c in cands}
41
+ if len(uniq) == 1:
42
+ v = next(iter(uniq.values()))
43
+ hit = (v["id"], v["label"], v["ontology"])
44
+ elif len(uniq) > 1:
45
+ return Resolved(text=text, ontology=None, confidence=0.3, candidates=list(uniq.values()),
46
+ note="ambiguous cell term; resolve to one of the candidates")
47
+ else:
48
+ return Resolved(text=text, confidence=None, note="unresolved cell term")
49
+ cid, label, onto = hit
50
+ note = _LINE_CAVEAT if onto == "Cellosaurus" else None
51
+ return Resolved(text=text, id=cid, label=label, ontology=onto, confidence=0.95, note=note)