pen-stack 0.1.0__py3-none-any.whl

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 (259) hide show
  1. pen_stack/__init__.py +2 -0
  2. pen_stack/_resources.py +34 -0
  3. pen_stack/active/__init__.py +20 -0
  4. pen_stack/active/acquire.py +165 -0
  5. pen_stack/active/brains.py +74 -0
  6. pen_stack/active/campaign.py +109 -0
  7. pen_stack/active/design.py +66 -0
  8. pen_stack/active/validate.py +104 -0
  9. pen_stack/adapt/__init__.py +14 -0
  10. pen_stack/adapt/finetune.py +33 -0
  11. pen_stack/adapt/ingest.py +86 -0
  12. pen_stack/adapt/pipeline.py +101 -0
  13. pen_stack/adapt/recalibrate.py +58 -0
  14. pen_stack/adapt/report.py +130 -0
  15. pen_stack/agent/__init__.py +1 -0
  16. pen_stack/agent/cite.py +175 -0
  17. pen_stack/agent/co_scientist.py +262 -0
  18. pen_stack/agent/epistemic.py +102 -0
  19. pen_stack/agent/guardrails.py +67 -0
  20. pen_stack/agent/mcp_server.py +215 -0
  21. pen_stack/agent/orchestrator.py +112 -0
  22. pen_stack/agent/orchestrator_live.py +56 -0
  23. pen_stack/agent/pen_agent.py +242 -0
  24. pen_stack/agent/scope.py +60 -0
  25. pen_stack/agent/tools.py +130 -0
  26. pen_stack/api/__init__.py +12 -0
  27. pen_stack/api/manifest.py +160 -0
  28. pen_stack/atlas/__init__.py +1 -0
  29. pen_stack/atlas/atlas.parquet +0 -0
  30. pen_stack/atlas/build_wtkb.py +80 -0
  31. pen_stack/atlas/crosslink.py +179 -0
  32. pen_stack/atlas/expand.py +190 -0
  33. pen_stack/atlas/guide_design.py +178 -0
  34. pen_stack/atlas/schema.py +59 -0
  35. pen_stack/atlas/scorecard.py +134 -0
  36. pen_stack/atlas/scorecard_v3.parquet +0 -0
  37. pen_stack/atlas/universe.py +75 -0
  38. pen_stack/atlas/universe_v3.parquet +0 -0
  39. pen_stack/atlas/variant_propose.py +155 -0
  40. pen_stack/atlas/writer_efficiency.py +184 -0
  41. pen_stack/atlas/writer_predict.py +229 -0
  42. pen_stack/atlas/writer_recommend.py +170 -0
  43. pen_stack/atlas/writer_verify.py +167 -0
  44. pen_stack/atlas/wtkb.parquet +0 -0
  45. pen_stack/bridge/__init__.py +1 -0
  46. pen_stack/bridge/activity.py +52 -0
  47. pen_stack/bridge/cli.py +65 -0
  48. pen_stack/bridge/fold_qc.py +53 -0
  49. pen_stack/bridge/guide_qc.py +87 -0
  50. pen_stack/bridge/ingest.py +139 -0
  51. pen_stack/bridge/offtarget.py +191 -0
  52. pen_stack/bridge/offtarget_energetics.py +105 -0
  53. pen_stack/bridge/ortholog_screen.py +73 -0
  54. pen_stack/bridge/pipeline.py +83 -0
  55. pen_stack/build/__init__.py +16 -0
  56. pen_stack/build/cloudlab.py +74 -0
  57. pen_stack/build/ingest.py +47 -0
  58. pen_stack/build/protocol.py +82 -0
  59. pen_stack/build/simlab.py +30 -0
  60. pen_stack/cli.py +126 -0
  61. pen_stack/data/__init__.py +1 -0
  62. pen_stack/data/encode.py +84 -0
  63. pen_stack/data/genome.py +71 -0
  64. pen_stack/data/ingest_chromatin.py +119 -0
  65. pen_stack/data/ingest_integration.py +112 -0
  66. pen_stack/data/ingest_safety_annot.py +201 -0
  67. pen_stack/data/ingest_trip.py +76 -0
  68. pen_stack/design/__init__.py +14 -0
  69. pen_stack/design/capsid_generate.py +62 -0
  70. pen_stack/design/generate.py +70 -0
  71. pen_stack/design/pareto.py +70 -0
  72. pen_stack/design/space.py +137 -0
  73. pen_stack/design/writer_variants.py +121 -0
  74. pen_stack/env/__init__.py +1 -0
  75. pen_stack/env/genome_writing_env.py +248 -0
  76. pen_stack/env/policies.py +94 -0
  77. pen_stack/graph/__init__.py +21 -0
  78. pen_stack/graph/build.py +133 -0
  79. pen_stack/graph/cell_types.py +58 -0
  80. pen_stack/graph/ingest.py +132 -0
  81. pen_stack/graph/query.py +148 -0
  82. pen_stack/graph/schema.py +100 -0
  83. pen_stack/loop/__init__.py +15 -0
  84. pen_stack/loop/continual.py +61 -0
  85. pen_stack/loop/cycle.py +84 -0
  86. pen_stack/loop/drift.py +41 -0
  87. pen_stack/mech/__init__.py +1 -0
  88. pen_stack/mech/classify_atlas.py +71 -0
  89. pen_stack/mech/pfam_whitelist.yaml +247 -0
  90. pen_stack/mech/whitelist.py +66 -0
  91. pen_stack/monitor/__init__.py +1 -0
  92. pen_stack/monitor/europepmc.py +32 -0
  93. pen_stack/monitor/run.py +57 -0
  94. pen_stack/monitor/triage.py +63 -0
  95. pen_stack/oracles/__init__.py +65 -0
  96. pen_stack/oracles/affinity.py +116 -0
  97. pen_stack/oracles/cache.py +53 -0
  98. pen_stack/oracles/energetics.py +33 -0
  99. pen_stack/oracles/genome.py +167 -0
  100. pen_stack/oracles/protein_design.py +136 -0
  101. pen_stack/oracles/reliability.py +64 -0
  102. pen_stack/oracles/rna.py +28 -0
  103. pen_stack/oracles/schema.py +77 -0
  104. pen_stack/oracles/status.py +123 -0
  105. pen_stack/oracles/structure.py +42 -0
  106. pen_stack/oracles/structure_run.py +76 -0
  107. pen_stack/oracles/vcell.py +74 -0
  108. pen_stack/planner/__init__.py +1 -0
  109. pen_stack/planner/ada_risk.py +64 -0
  110. pen_stack/planner/antipeg_oracle.py +75 -0
  111. pen_stack/planner/capsid_epitope_oracle.py +135 -0
  112. pen_stack/planner/cargo.py +56 -0
  113. pen_stack/planner/cargo_polish.py +146 -0
  114. pen_stack/planner/chromosome.py +106 -0
  115. pen_stack/planner/delivery.py +55 -0
  116. pen_stack/planner/delivery_constraints.py +110 -0
  117. pen_stack/planner/delivery_immune.py +61 -0
  118. pen_stack/planner/delivery_immunology.py +222 -0
  119. pen_stack/planner/delivery_predict.py +196 -0
  120. pen_stack/planner/delivery_vehicles.py +37 -0
  121. pen_stack/planner/genotoxicity_oracle.py +112 -0
  122. pen_stack/planner/immune_mhc2.py +154 -0
  123. pen_stack/planner/immune_profile.py +292 -0
  124. pen_stack/planner/innate_sensing.py +135 -0
  125. pen_stack/planner/multiplex.py +110 -0
  126. pen_stack/planner/optimize.py +278 -0
  127. pen_stack/planner/pipeline.py +87 -0
  128. pen_stack/planner/report.py +26 -0
  129. pen_stack/planner/router.py +57 -0
  130. pen_stack/planner/seroprevalence_oracle.py +92 -0
  131. pen_stack/planner/target_site.py +118 -0
  132. pen_stack/rag/__init__.py +1 -0
  133. pen_stack/rag/corpus.py +133 -0
  134. pen_stack/rag/embed.py +98 -0
  135. pen_stack/rag/ground.py +131 -0
  136. pen_stack/rag/index.py +53 -0
  137. pen_stack/rag/llm.py +215 -0
  138. pen_stack/rag/qa.py +105 -0
  139. pen_stack/rag/retrieve.py +48 -0
  140. pen_stack/rules/__init__.py +9 -0
  141. pen_stack/rules/evaluators.py +318 -0
  142. pen_stack/rules/loader.py +31 -0
  143. pen_stack/rules/schema.py +99 -0
  144. pen_stack/rules/solver.py +43 -0
  145. pen_stack/rules/spec.py +78 -0
  146. pen_stack/safety/__init__.py +21 -0
  147. pen_stack/safety/audit.py +90 -0
  148. pen_stack/safety/gate.py +58 -0
  149. pen_stack/safety/pfam_scan.py +157 -0
  150. pen_stack/safety/policy.py +69 -0
  151. pen_stack/safety/redteam.py +71 -0
  152. pen_stack/safety/registry.py +255 -0
  153. pen_stack/safety/screen.py +59 -0
  154. pen_stack/safety/standards.py +141 -0
  155. pen_stack/score/__init__.py +1 -0
  156. pen_stack/score/recalibrate.py +77 -0
  157. pen_stack/score/therapeutic.py +85 -0
  158. pen_stack/server/__init__.py +1 -0
  159. pen_stack/server/api.py +647 -0
  160. pen_stack/spec/__init__.py +18 -0
  161. pen_stack/spec/clarify.py +42 -0
  162. pen_stack/spec/extract.py +406 -0
  163. pen_stack/spec/resolvers/__init__.py +17 -0
  164. pen_stack/spec/resolvers/cell.py +51 -0
  165. pen_stack/spec/resolvers/chem.py +32 -0
  166. pen_stack/spec/resolvers/feature.py +36 -0
  167. pen_stack/spec/resolvers/gene.py +43 -0
  168. pen_stack/spec/resolvers/locus.py +29 -0
  169. pen_stack/spec/resolvers/phenotype.py +37 -0
  170. pen_stack/spec/satisfy.py +114 -0
  171. pen_stack/spec/service.py +33 -0
  172. pen_stack/spec/writespec.py +252 -0
  173. pen_stack/twin/__init__.py +14 -0
  174. pen_stack/twin/calibrate.py +61 -0
  175. pen_stack/twin/data/__init__.py +12 -0
  176. pen_stack/twin/data/position_effect.py +245 -0
  177. pen_stack/twin/mechanistic.py +147 -0
  178. pen_stack/twin/outcome.py +132 -0
  179. pen_stack/twin/position_effect.py +454 -0
  180. pen_stack/ui/__init__.py +1 -0
  181. pen_stack/ui/app.py +713 -0
  182. pen_stack/validate/__init__.py +1 -0
  183. pen_stack/validate/adapt_demo.py +69 -0
  184. pen_stack/validate/agent_eval.py +117 -0
  185. pen_stack/validate/bench_adversarial_tasks.py +118 -0
  186. pen_stack/validate/bench_coscientist_tasks.py +60 -0
  187. pen_stack/validate/bench_graph_tasks.py +64 -0
  188. pen_stack/validate/bench_rule_tasks.py +84 -0
  189. pen_stack/validate/bench_trust_tasks.py +92 -0
  190. pen_stack/validate/bench_writetype_tasks.py +101 -0
  191. pen_stack/validate/blind_gsh_discovery.py +261 -0
  192. pen_stack/validate/cargo_directionality.py +57 -0
  193. pen_stack/validate/closed_loop.py +63 -0
  194. pen_stack/validate/durability_baselines.py +185 -0
  195. pen_stack/validate/experiment_design.py +65 -0
  196. pen_stack/validate/expr_controls.py +39 -0
  197. pen_stack/validate/forward_hypotheses.py +104 -0
  198. pen_stack/validate/generative_design.py +62 -0
  199. pen_stack/validate/guide_qc_demo.py +69 -0
  200. pen_stack/validate/heldout_celltype_expr.py +32 -0
  201. pen_stack/validate/immune_calibration.py +133 -0
  202. pen_stack/validate/intent_specification.py +82 -0
  203. pen_stack/validate/known_biology_expr.py +38 -0
  204. pen_stack/validate/offtarget_energetics_eval.py +144 -0
  205. pen_stack/validate/out_of_scope_refusal.py +82 -0
  206. pen_stack/validate/outcome_calibration.py +194 -0
  207. pen_stack/validate/outcome_prediction.py +76 -0
  208. pen_stack/validate/paper3_benchmark.py +165 -0
  209. pen_stack/validate/paper4_real_validation.py +144 -0
  210. pen_stack/validate/paper4_validation.py +82 -0
  211. pen_stack/validate/protocol_safety.py +62 -0
  212. pen_stack/validate/safety_screening.py +72 -0
  213. pen_stack/validate/selective_prediction.py +104 -0
  214. pen_stack/validate/seq_vs_measured.py +134 -0
  215. pen_stack/validate/target_site_controls.py +65 -0
  216. pen_stack/validate/uncertainty_eval.py +244 -0
  217. pen_stack/validate/ungrounded_baseline.py +234 -0
  218. pen_stack/validate/within_locus_ranking.py +84 -0
  219. pen_stack/validate/writer_recovery.py +91 -0
  220. pen_stack/verify/__init__.py +5 -0
  221. pen_stack/verify/proof.py +206 -0
  222. pen_stack/verify/schema.py +53 -0
  223. pen_stack/verify/service.py +191 -0
  224. pen_stack/web/__init__.py +18 -0
  225. pen_stack/web/guide.py +110 -0
  226. pen_stack/web/llm.py +393 -0
  227. pen_stack/web/llm_provider.py +119 -0
  228. pen_stack/web/router.py +119 -0
  229. pen_stack/web/server.py +96 -0
  230. pen_stack/web/tools.py +197 -0
  231. pen_stack/wgenome/__init__.py +1 -0
  232. pen_stack/wgenome/chromatin_seq.py +83 -0
  233. pen_stack/wgenome/durability.py +108 -0
  234. pen_stack/wgenome/export_tracks.py +52 -0
  235. pen_stack/wgenome/features.py +82 -0
  236. pen_stack/wgenome/genotoxic_blocklist.py +88 -0
  237. pen_stack/wgenome/gsh_baseline.py +154 -0
  238. pen_stack/wgenome/mesh_features.py +61 -0
  239. pen_stack/wgenome/offtarget_assay.py +80 -0
  240. pen_stack/wgenome/offtarget_bridge.py +47 -0
  241. pen_stack/wgenome/offtarget_cast.py +97 -0
  242. pen_stack/wgenome/offtarget_data.py +148 -0
  243. pen_stack/wgenome/offtarget_enumerate.py +274 -0
  244. pen_stack/wgenome/offtarget_integrase.py +155 -0
  245. pen_stack/wgenome/offtarget_nuclease.py +123 -0
  246. pen_stack/wgenome/offtarget_paste.py +41 -0
  247. pen_stack/wgenome/offtarget_predict.py +282 -0
  248. pen_stack/wgenome/ood.py +135 -0
  249. pen_stack/wgenome/providers.py +278 -0
  250. pen_stack/wgenome/safety.py +69 -0
  251. pen_stack/wgenome/structure3d.py +212 -0
  252. pen_stack/wgenome/uncertainty.py +250 -0
  253. pen_stack/wgenome/writability.py +72 -0
  254. pen_stack-0.1.0.dist-info/METADATA +401 -0
  255. pen_stack-0.1.0.dist-info/RECORD +259 -0
  256. pen_stack-0.1.0.dist-info/WHEEL +5 -0
  257. pen_stack-0.1.0.dist-info/entry_points.txt +3 -0
  258. pen_stack-0.1.0.dist-info/licenses/LICENSE +21 -0
  259. pen_stack-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,206 @@
1
+ """Repair-oriented proof object for verify().
2
+
3
+ ``verify(design)`` already returns legality, a calibrated confidence and a biosecurity verdict as separate
4
+ fields. ``verify_proof(design)`` repackages that into a machine-readable proof an agent can repair a design
5
+ from: three SEPARATELY-REPORTED axes (legality, confidence, biosecurity), each carrying a status, the rule or
6
+ signature that fired, the supporting evidence, and a repair hint. The single collapsed verdict is always
7
+ ``None`` (the axes are never fused), per the verification-service invariant.
8
+
9
+ Repair hints are actionable for legality (a structured field edit that an agent, or ``repair_from_proof``, can
10
+ apply and re-verify) and for the confidence abstention (which scores to supply). Biosecurity repair hints are
11
+ deliberately NON-ACTIONABLE for a hazard: a refused or escalated design is acknowledged and routed to human
12
+ biosafety review, never auto-repaired and never given instructions toward the hazard. Generation may be
13
+ intractable; verification, and repairing a failed-on-legality design, is tractable, which is the point.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+ from pen_stack.planner.delivery_vehicles import names as _vehicle_names
22
+ from pen_stack.planner.delivery_vehicles import vehicle as _vehicle
23
+ from pen_stack.rules import Design
24
+ from pen_stack.verify.schema import Verdict
25
+ from pen_stack.verify.service import verify
26
+
27
+ AXES = ("legality", "confidence", "biosecurity")
28
+
29
+
30
+ class AxisProof(BaseModel):
31
+ axis: str # one of AXES
32
+ status: str # pass | fail | abstain | refuse | escalate | flag | deferred
33
+ ok: bool # True when this axis does not block (pass/flag/clear; abstain is ok-but-uncertain for confidence)
34
+ violated: list[dict[str, Any]] = Field(default_factory=list) # the rule(s)/signature(s) that fired
35
+ evidence: dict[str, Any] = Field(default_factory=dict)
36
+ repair_hint: dict[str, Any] | None = None # {text, repair?: {field, set_to}} | None
37
+
38
+
39
+ class Proof(BaseModel):
40
+ design: dict[str, Any]
41
+ write_type: str
42
+ axes: list[AxisProof]
43
+ collapsed: None = None # the three axes are NEVER fused into one verdict
44
+ passable: bool # legality passes AND biosecurity does not refuse/escalate (confidence may abstain)
45
+ no_fabrication: bool = True
46
+ provenance: dict[str, Any] = Field(default_factory=dict)
47
+
48
+ def axis(self, name: str) -> AxisProof:
49
+ return next(a for a in self.axes if a.axis == name)
50
+
51
+
52
+ def _pick_vehicle(cargo_bp: int | None, form: str | None, non_integrating: bool) -> str | None:
53
+ """Smallest-capacity vehicle that carries ``form``, fits ``cargo_bp``, and respects an integration goal.
54
+ Deterministic; used to turn a legality violation into a concrete repair."""
55
+ best: tuple[str, float] | None = None
56
+ for name in _vehicle_names():
57
+ rec = _vehicle(name) or {}
58
+ forms = rec.get("compatible_cargo_form") or []
59
+ if form and form not in forms:
60
+ continue
61
+ if non_integrating and rec.get("integrating"):
62
+ continue
63
+ cap = rec.get("cargo_capacity_bp")
64
+ if cargo_bp is not None and cap is not None and cap < cargo_bp:
65
+ continue
66
+ score = float(cap) if cap is not None else float("inf")
67
+ if best is None or score < best[1]:
68
+ best = (name, score)
69
+ return best[0] if best else None
70
+
71
+
72
+ def _repair_for_violation(d: Design, rule_id: str) -> dict[str, Any] | None:
73
+ """A structured, applyable repair for a repairable legality violation, or None if not auto-repairable."""
74
+ form = d.writer_output_form or ("DNA" if d.write_type in ("insertion", "replacement", "landing_pad") else None)
75
+ if rule_id in ("payload.cargo_within_capacity", "delivery.cargo_form_compatible"):
76
+ veh = _pick_vehicle(d.cargo_bp, form, non_integrating=bool(d.no_integration))
77
+ if veh and veh != d.delivery_vehicle:
78
+ why = ("cargo exceeds the vehicle packaging capacity" if "capacity" in rule_id
79
+ else "the cargo form is not compatible with the vehicle")
80
+ return {"text": f"{why}; switch to a vehicle that fits ({veh}).",
81
+ "repair": {"field": "delivery_vehicle", "set_to": veh}}
82
+ if rule_id == "delivery.no_integration_constraint":
83
+ veh = _pick_vehicle(d.cargo_bp, form, non_integrating=True)
84
+ if veh and veh != d.delivery_vehicle:
85
+ return {"text": f"the goal forbids integration but the vehicle integrates; switch to {veh}.",
86
+ "repair": {"field": "delivery_vehicle", "set_to": veh}}
87
+ if rule_id == "reachability.target_element_available":
88
+ return {"text": "the writer cannot reach the target as specified; declare a pre-installed landing pad "
89
+ "(installed_att) or choose a writer family whose target element is present.",
90
+ "repair": {"field": "installed_att", "set_to": True}}
91
+ if rule_id == "compliance.germline_prohibition":
92
+ return {"text": "this is a heritable / germline edit, which is out of scope for this somatic-editing "
93
+ "tool and is broadly prohibited (international moratorium); restrict to SOMATIC editing - "
94
+ "move to ex vivo (in_vivo = false) and/or a somatic cell type (e.g. hepatocytes / HepG2, "
95
+ "HSPCs), and drop any germline/heritable intent. (Not auto-repairable: a germline edit is "
96
+ "a change of intent, not a field fix.)",
97
+ "repair": None}
98
+ return None
99
+
100
+
101
+ _SAFETY_REFUSED_SOURCE = "safety_gate(refused)" # the exact provenance.source verify.service sets on early-return
102
+
103
+
104
+ def _legality_axis(d: Design, v: Verdict) -> AxisProof:
105
+ if v.provenance.get("source") == _SAFETY_REFUSED_SOURCE:
106
+ # biosecurity refused BEFORE the rules engine ran (verify.service short-circuits on refuse), legality
107
+ # was never evaluated, so no rule fired and there is nothing to cite. Reporting "fail" here would
108
+ # fabricate a legality verdict for an axis that never ran. State: unknown, not itself blocking
109
+ # (biosecurity already blocks `passable`), mirrors the "abstaining is a deferral, not a block" convention
110
+ # already used by the confidence axis.
111
+ return AxisProof(axis="legality", status="not_evaluated", ok=True,
112
+ evidence={"reason": "biosecurity refused this design before legality was evaluated"},
113
+ repair_hint={"text": "legality was not evaluated, biosecurity refused this design "
114
+ "first. Legality can only be checked once the biosecurity concern "
115
+ "is resolved (route to institutional biosafety / IBC review).",
116
+ "repair": None})
117
+ if v.deferred:
118
+ return AxisProof(axis="legality", status="deferred", ok=False,
119
+ evidence={"reason": v.routing.get("reason", "unsupported write type")},
120
+ repair_hint={"text": "the write type is not yet supported; route to a supported type."})
121
+ if v.legal:
122
+ return AxisProof(axis="legality", status="pass", ok=True,
123
+ evidence={"rules_version": v.provenance.get("rules_version")})
124
+ first = v.violations[0] if v.violations else None
125
+ hint = _repair_for_violation(d, first["rule_id"]) if first else None
126
+ return AxisProof(axis="legality", status="fail", ok=False,
127
+ violated=[{"rule_id": x["rule_id"], "reason": x["reason"], "citation": x.get("citation", [])}
128
+ for x in v.violations],
129
+ evidence={"n_violations": len(v.violations)}, repair_hint=hint)
130
+
131
+
132
+ def _confidence_axis(v: Verdict) -> AxisProof:
133
+ if v.provenance.get("source") == _SAFETY_REFUSED_SOURCE:
134
+ # same short-circuit: confidence was never computed. The "uncalibrated" repair hint below would be
135
+ # ACTIVELY MISLEADING here, supplying the soft scores would not help, since the block happens before
136
+ # they would ever be read.
137
+ return AxisProof(axis="confidence", status="not_evaluated", ok=True,
138
+ evidence={"confidence": None, "interval": None,
139
+ "reason": "biosecurity refused this design before confidence was evaluated"},
140
+ repair_hint={"text": "confidence was not evaluated, biosecurity refused this design "
141
+ "before reaching the confidence check.", "repair": None})
142
+ if v.confidence is None:
143
+ return AxisProof(axis="confidence", status="abstain", ok=True, # abstaining is a deferral, not a block
144
+ evidence={"confidence": None, "interval": None},
145
+ repair_hint={"text": "confidence is uncalibrated for this design; supply the soft "
146
+ "per-axis scores (safety, p_durable, writer_activity) to calibrate."})
147
+ return AxisProof(axis="confidence", status="pass", ok=True,
148
+ evidence={"confidence": v.confidence, "interval": v.interval,
149
+ "epistemic_status": v.epistemic_status})
150
+
151
+
152
+ def _biosecurity_axis(v: Verdict) -> AxisProof:
153
+ s = v.safety
154
+ if s is None:
155
+ return AxisProof(axis="biosecurity", status="pass", ok=True, evidence={"decision": "clear"})
156
+ blocked = s.decision in ("refuse", "escalate")
157
+ # Carry the same detail Guardian's own /api/safety view shows (which signature, and for a
158
+ # sequence-domain hit, which Pfam accession + bit score) -- Design Studio's Proof was previously
159
+ # stripping this down to a bare signature id with no reason/citation/evidence.
160
+ sigs = [{"signature": h.provenance.get("signature_id"), "kind": h.kind, "severity": h.severity,
161
+ "reason": h.detail, "citation": [c for c in [h.provenance.get("control_ref")] if c],
162
+ **({"pfam_accession": h.evidence.get("pfam_accession"), "bit_score": h.evidence.get("bit_score")}
163
+ if h.kind == "sequence_homology" else {})}
164
+ for h in (s.hits or [])]
165
+ # repair hints for a hazard are deliberately NON-actionable: acknowledge and route to human review.
166
+ hint = None
167
+ if blocked:
168
+ hint = {"text": "this design matches a controlled-hazard signature; it is not auto-repairable. "
169
+ "Route to institutional biosafety / IBC review. (No actionable repair is provided.)",
170
+ "repair": None}
171
+ return AxisProof(axis="biosecurity", status=s.decision, ok=not blocked,
172
+ violated=sigs, evidence={"decision": s.decision, "reason": s.reason}, repair_hint=hint)
173
+
174
+
175
+ def verify_proof(design: Design | dict, question: str | None = None, *, actor: str = "anonymous") -> Proof:
176
+ """Verify a design and return the per-axis proof object. The three axes are reported separately and the
177
+ collapsed verdict is always None. ``passable`` is True when legality passes and biosecurity does not
178
+ refuse/escalate (an abstaining confidence axis does not block)."""
179
+ d = Design(**dict(design)) if isinstance(design, dict) else design
180
+ v = verify(design, question=question, actor=actor)
181
+ axes = [_legality_axis(d, v), _confidence_axis(v), _biosecurity_axis(v)]
182
+ legality_ok = axes[0].ok
183
+ biosec_ok = axes[2].ok
184
+ return Proof(design=d.model_dump(exclude_none=True), write_type=d.write_type, axes=axes,
185
+ passable=bool(legality_ok and biosec_ok),
186
+ provenance={"source": "verify_proof", "rules_version": v.provenance.get("rules_version")})
187
+
188
+
189
+ def repair_from_proof(design: Design | dict, proof: Proof) -> Design:
190
+ """Apply the structured repairs carried by the proof's axes (legality first) and return the repaired
191
+ Design. Uses ONLY the proof object, never re-inspecting the engine, so it demonstrates the proof is
192
+ agent-repairable. Biosecurity hazards are never auto-repaired (their repair is None)."""
193
+ d = Design(**dict(design)) if isinstance(design, dict) else Design(**design.model_dump())
194
+ patch: dict[str, Any] = {}
195
+ for ax in proof.axes:
196
+ hint = ax.repair_hint
197
+ if not hint:
198
+ continue
199
+ rep = hint.get("repair")
200
+ if rep and rep.get("field") is not None:
201
+ patch[rep["field"]] = rep["set_to"]
202
+ if not patch:
203
+ return d
204
+ data = d.model_dump()
205
+ data.update(patch)
206
+ return Design(**data)
@@ -0,0 +1,53 @@
1
+ """The Verdict object returned by the verification service.
2
+
3
+ Carries legality + the named rejections + a calibrated confidence + an epistemic status + scope flags, with
4
+ legality and confidence kept as separate fields (never collapsed). Serializable for REST/MCP.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field
11
+
12
+ from pen_stack.safety.policy import SafetyVerdict
13
+
14
+
15
+ class Verdict(BaseModel):
16
+ """Frozen: the verification result is immutable once returned, so legality, the named violations, the
17
+ calibrated confidence, and the nested (also-frozen) safety verdict cannot be rewritten after the fact.
18
+ Build a changed verdict with `model_copy(update=...)` if a transform genuinely needs one."""
19
+ model_config = ConfigDict(frozen=True)
20
+
21
+ legal: bool | None # True/False; None when the write type is deferred
22
+ deferred: bool = False
23
+ write_type: str
24
+ routing: dict[str, Any] = Field(default_factory=dict)
25
+ rule_results: list[dict] = Field(default_factory=list)
26
+ violations: list[dict] = Field(default_factory=list) # named hard-rule rejections + citation
27
+ soft_flags: list[dict] = Field(default_factory=list) # soft penalties raised
28
+ scope_flags: list[dict] = Field(default_factory=list) # known-unknowns + rule scope flags
29
+ confidence: float | None = None # calibrated confidence on the SOFT components (distinct axis)
30
+ interval: list[float] | None = None
31
+ epistemic_status: str = "not-computable"
32
+ provenance: dict[str, Any] = Field(default_factory=dict)
33
+ no_fabrication: bool = True
34
+ writer_critique: dict[str, Any] | None = None # critique of a generated candidate writer
35
+ # (pass/flag + reasons); NEVER a claim that it works
36
+ delivery_profile: dict[str, Any] | None = None # documented ordinal immune/safety/efficacy
37
+ # priors for the chosen vehicle (NEVER a predicted magnitude)
38
+ immune_profile: dict[str, Any] | None = None # per-axis immune-risk vector (genotox/CD8/
39
+ # innate/NAb/anti-PEG), each w/ own uncertainty + validation
40
+ # label; collapsed_score is None (never fused); magnitude KU
41
+ safety: SafetyVerdict | None = None # the Guardian: biosecurity/dual-use screen
42
+ # (clear/flag/refuse/escalate); a refused design is NOT
43
+ # evaluated further; orthogonal to immune_profile
44
+
45
+ def summary(self) -> str:
46
+ if self.safety is not None and self.safety.decision == "refuse":
47
+ return f"REFUSED (safety): {self.safety.reason}"
48
+ if self.deferred:
49
+ return f"DEFERRED ({self.write_type}): {self.routing.get('reason', 'unsupported write type')}"
50
+ verdict = "LEGAL" if self.legal else "ILLEGAL"
51
+ conf = f"conf={self.confidence}" if self.confidence is not None else "conf=n/a (abstained)"
52
+ why = "" if self.legal else " | " + "; ".join(v["rule_id"] for v in self.violations)
53
+ return f"{verdict} [{self.epistemic_status}, {conf}]{why}"
@@ -0,0 +1,191 @@
1
+ """The verification service, `verify(design) -> Verdict`.
2
+
3
+ The first-class "for the AI" surface: submit a proposed genomic write, get back a structured verdict,
4
+ legality (from the rule solver), the named reason + citation for any rejection, a calibrated confidence
5
+ on the *soft* components (from the L4 trust layer), an epistemic status, and any out-of-scope flags.
6
+ Thin orchestration over existing layers; it generates **no new numbers** (no-fabrication holds: legality is
7
+ rule-deterministic, confidence comes from the calibrated tool, scope from the registry).
8
+
9
+ Legality and confidence are DISTINCT axes (Principle 2) and are never collapsed: a design can be
10
+ legal-but-low-confidence or illegal-with-certainty. The Verdict carries both, separately.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+ from pen_stack.rules import Design
17
+ from pen_stack.rules.loader import RULES_VERSION
18
+ from pen_stack.verify.schema import Verdict
19
+
20
+
21
+ def _plan_confidence(design: Design) -> dict:
22
+ """Calibrated plan confidence on the SOFT components, only when the design carries the per-axis scores
23
+ (safety / p_durable / writer_activity). Otherwise None (abstain), never fabricated."""
24
+ extra = design.model_extra or {}
25
+ keys = ("safety", "p_durable", "writer_activity")
26
+ if not all(k in extra and extra[k] is not None for k in keys):
27
+ return {"confidence": None, "interval": None}
28
+ import pandas as pd
29
+
30
+ from pen_stack.planner.optimize import attach_uncertainty
31
+ row = pd.DataFrame([{"chrom": design.chrom or "chr?", "bin": 0,
32
+ "safety": float(extra["safety"]), "p_durable": float(extra["p_durable"]),
33
+ "writer_activity": float(extra["writer_activity"]), "score": 0.0}])
34
+ intent = design.edit_intent or "safe_harbour_insertion"
35
+ out = attach_uncertainty(row, intent, ood_factor=float(extra.get("ood_factor", 1.0)))
36
+ r = out.iloc[0]
37
+ return {"confidence": float(r["confidence"]), "interval": [float(r["score_lo"]), float(r["score_hi"])]}
38
+
39
+
40
+ def verify(design: Design | dict, question: str | None = None, *, actor: str = "anonymous") -> Verdict:
41
+ """Verify a proposed write. ``design`` may be a Design or a plain dict (e.g. from REST/MCP JSON).
42
+
43
+ The Guardian: a biosecurity / dual-use safety gate runs FIRST. A design that matches a high-severity
44
+ hazard signature is REFUSED and returned un-evaluated (not scored further); every call is logged to the
45
+ tamper-evident audit trail. The safety verdict is orthogonal to the immune profile and is attached to
46
+ every Verdict. ``actor`` is recorded in the audit log."""
47
+ if isinstance(design, dict):
48
+ d = dict(design)
49
+ question = question or d.pop("question", None)
50
+ screen_payload = dict(d)
51
+ design = Design(**d)
52
+ else:
53
+ screen_payload = {**design.model_dump(exclude_none=False), **(design.model_extra or {})}
54
+
55
+ # The safety gate is the first thing every caller inherits.
56
+ from pen_stack.safety.gate import safety_gate
57
+ safety = safety_gate(screen_payload, actor=actor)
58
+ if safety.decision == "refuse":
59
+ return Verdict(
60
+ legal=None, deferred=False, write_type=design.write_type, routing={},
61
+ rule_results=[], violations=[], soft_flags=[],
62
+ scope_flags=[{"kind": "safety_refused", "reason": safety.reason,
63
+ "signatures": [h.provenance.get("signature_id") for h in safety.hits]}],
64
+ confidence=None, interval=None, epistemic_status="refused",
65
+ provenance={"rules_version": RULES_VERSION, "source": "safety_gate(refused)"},
66
+ no_fabrication=True, safety=safety)
67
+
68
+ from pen_stack.agent.epistemic import classify
69
+ from pen_stack.planner.router import route_and_evaluate
70
+
71
+ routed = route_and_evaluate(design)
72
+ routing = routed["routing"]
73
+
74
+ # scope: out-of-scope question (known-unknowns) + rule scope_flags
75
+ scope_flags: list[dict[str, Any]] = []
76
+ oos_hit = False
77
+ if question:
78
+ from pen_stack.agent.scope import match_scope
79
+ m = match_scope(question)
80
+ if m:
81
+ oos_hit = True
82
+ scope_flags.append({"kind": "known_unknown", "id": m["id"], "reason": m["deferral"]})
83
+
84
+ if routed["deferred"]:
85
+ verdict = classify(grounded=False, out_of_scope=True)
86
+ return Verdict(
87
+ legal=None, deferred=True, write_type=design.write_type, routing=routing,
88
+ rule_results=[], violations=[], soft_flags=[],
89
+ scope_flags=scope_flags + [{"kind": "unsupported_write_type", "reason": routing["reason"]}],
90
+ confidence=None, interval=None, epistemic_status=verdict.status,
91
+ provenance={"rules_version": RULES_VERSION, "source": "router(deferred)"}, no_fabrication=True,
92
+ safety=safety)
93
+
94
+ results = routed["rule_results"]
95
+ violations = [r for r in results if r["kind"] == "hard_reject" and r["status"] == "violate"]
96
+ soft = [r for r in results if r["kind"] == "soft_penalty" and r["status"] == "flag"]
97
+ rule_scope = [{"kind": "rule_scope", "rule_id": r["rule_id"], "reason": r["reason"]}
98
+ for r in results if r["kind"] == "scope_flag" and r["status"] == "scope"]
99
+ scope_flags += rule_scope
100
+
101
+ # Chromosome validation + gene/chromosome concordance + chromosome-context advisory. The free-text
102
+ # chrom field does not move the scored locus (scoring is indexed by the gene's resolved coordinates), so a
103
+ # mismatch / invalid value / chromosome-context note is surfaced as a scope flag, never silently ignored.
104
+ from pen_stack.planner.chromosome import chromosome_concordance, chromosome_context
105
+ conc = chromosome_concordance(design.gene, getattr(design, "chrom", None))
106
+ if conc["status"] in ("invalid", "mismatch", "unverifiable"):
107
+ scope_flags.append({"kind": f"chromosome_{conc['status']}", "reason": conc["message"],
108
+ "entered": conc["entered"], "gene_chrom": conc["gene_chrom"]})
109
+ ctx = chromosome_context(getattr(design, "chrom", None))
110
+ if ctx:
111
+ scope_flags.append({"kind": "chromosome_context", "chrom": ctx["chrom"], "reason": ctx["note"]})
112
+
113
+ pc = _plan_confidence(design)
114
+ # A design with no calibrated confidence (the soft-axis scores were absent, so _plan_confidence abstained)
115
+ # must NOT read as grounded-confident: a missing confidence is an abstention, not a high-trust result.
116
+ verdict = classify(grounded=True, confidence=pc["confidence"],
117
+ abstained=pc["confidence"] is None, out_of_scope=oos_hit)
118
+
119
+ # If the design carries a GENERATED candidate writer sequence, critique it (fold / active
120
+ # site / deliverability / reachability), never a claim that it works. Adds a scope flag, never confidence.
121
+ writer_critique = None
122
+ cand_seq = (design.model_extra or {}).get("writer_candidate_seq")
123
+ if cand_seq:
124
+ from pen_stack.atlas.writer_verify import critique_candidate
125
+ writer_critique = critique_candidate(
126
+ cand_seq, writer_family=design.writer_family or "bridge_IS110",
127
+ delivery_vehicle=design.delivery_vehicle, no_integration=design.no_integration,
128
+ site_seq=design.site_seq)
129
+ scope_flags.append({"kind": "writer_candidate_critique", "pass": writer_critique["pass"],
130
+ "flags": writer_critique["flags"],
131
+ "reason": "generated writer is critiqued, never claimed to work"})
132
+
133
+ # If the design names a delivery vehicle, surface its DOCUMENTED ordinal immune/safety/
134
+ # efficacy priors (with the standing "magnitude is a known-unknown" scope flag). This never adds confidence
135
+ # and never predicts a magnitude, it exposes the curated qualitative tradeoff so safety can be weighed.
136
+ delivery_profile = None
137
+ if design.delivery_vehicle:
138
+ from pen_stack.planner.delivery_immunology import safety_efficacy_profile
139
+ delivery_profile = safety_efficacy_profile(design.delivery_vehicle)
140
+ if delivery_profile and delivery_profile.get("tradeoff"):
141
+ scope_flags.append({"kind": "delivery_immune_profile",
142
+ "vehicle": delivery_profile["vehicle"],
143
+ "tradeoff": delivery_profile["tradeoff"],
144
+ "magnitude_id": delivery_profile["magnitude_scope_flag"]["id"],
145
+ "reason": "documented ordinal immune/safety priors surfaced; the in-vivo immune "
146
+ "MAGNITUDE remains a known-unknown (not predicted)"})
147
+
148
+ # If a cargo SEQUENCE is supplied, compute its innate-sensing load from sequence
149
+ # (CpG/TLR9 for DNA, U/dsRNA for mRNA). Surfaced as a scope flag; the realized innate RESPONSE magnitude
150
+ # is a known-unknown. Cargo form = the writer output form, else the vehicle's first compatible form.
151
+ if design.cargo_seq:
152
+ form = design.writer_output_form
153
+ if not form and design.delivery_vehicle:
154
+ from pen_stack.planner.delivery_vehicles import vehicle as _veh
155
+ forms = (_veh(design.delivery_vehicle) or {}).get("compatible_cargo_form") or []
156
+ form = forms[0] if forms else None
157
+ if form:
158
+ from pen_stack.planner.innate_sensing import innate_sensing
159
+ inr = innate_sensing(design.cargo_seq, form)
160
+ if inr.available:
161
+ scope_flags.append({"kind": "cargo_innate_sensing", "cargo_form": form,
162
+ "innate_score": inr.value["innate_score"], "pathway": inr.value["pathway"],
163
+ "reason": "computed sequence-intrinsic innate-sensing load; the realized "
164
+ "in-vivo innate RESPONSE magnitude is a known-unknown"})
165
+ if delivery_profile is not None:
166
+ delivery_profile = dict(delivery_profile)
167
+ delivery_profile["cargo_innate"] = inr.value
168
+
169
+ # The unified per-axis immune-risk profile (genotox / CD8 epitope / innate / pre-existing
170
+ # NAb / anti-PEG), each with its own uncertainty + validation label; collapsed_score is None
171
+ # (never fused into one number); the in-vivo magnitude + patient titer stay known-unknowns.
172
+ immune_profile = None
173
+ if design.delivery_vehicle:
174
+ from pen_stack.planner.immune_profile import immune_profile as _immune_profile
175
+ extra = design.model_extra or {}
176
+ immune_profile = _immune_profile({
177
+ "delivery_vehicle": design.delivery_vehicle, "serotype": extra.get("serotype"),
178
+ "cargo_seq": design.cargo_seq, "writer_output_form": design.writer_output_form,
179
+ "pegylated": extra.get("pegylated")})
180
+
181
+ return Verdict(
182
+ legal=routed["legal"], deferred=False, write_type=design.write_type, routing=routing,
183
+ rule_results=results,
184
+ violations=[{"rule_id": v["rule_id"], "reason": v["reason"], "citation": v.get("citation", [])}
185
+ for v in violations],
186
+ soft_flags=[{"rule_id": s["rule_id"], "reason": s["reason"], "value": s.get("value")} for s in soft],
187
+ scope_flags=scope_flags,
188
+ confidence=pc["confidence"], interval=pc["interval"], epistemic_status=verdict.status,
189
+ provenance={"rules_version": RULES_VERSION, "source": "rules.solver + L4(uncertainty/scope/epistemic)"},
190
+ no_fabrication=True, writer_critique=writer_critique, delivery_profile=delivery_profile,
191
+ immune_profile=immune_profile, safety=safety)
@@ -0,0 +1,18 @@
1
+ """PEN-STACK Web Platform, the human surface.
2
+
3
+ A grounded co-scientist chat + every engine feature behind one typed gateway, with provenance-first UX. The LLM narrates
4
+ and routes; the engine sources every number. Submodules:
5
+
6
+ * ``tools``, the deterministic engine tool-runner (parse a goal, run verify/safety/immune/scope, return a
7
+ grounded dossier) and the grounding allow-list.
8
+ * ``llm``, the grounded co-scientist (Ollama → Nemotron → deterministic) with the grounding-guard hard gate.
9
+ * ``server``, the FastAPI gateway (mounts the engine surface + ``/chat``); needs the ``server`` extra.
10
+
11
+ ``server`` is imported lazily (it requires FastAPI) so ``import pen_stack.web`` works on a bare install.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from pen_stack.web.llm import grounded_reply
16
+ from pen_stack.web.tools import extract_grounded_numbers, parse_goal, run_tools
17
+
18
+ __all__ = ["grounded_reply", "run_tools", "parse_goal", "extract_grounded_numbers"]
pen_stack/web/guide.py ADDED
@@ -0,0 +1,110 @@
1
+ """Metric interpretation + PEN-STACK capability facts (the explain/meta lanes).
2
+
3
+ Two grounded sources the hybrid co-scientist narrates over WITHOUT fabricating:
4
+ * `metric_guide()`, curated interpretation cards (configs/metric_guide.yaml): what each engine number means,
5
+ its scale/direction, reference bands, how it is computed, validation status. Used to EXPLAIN a value.
6
+ * `pen_stack_facts()`, capability facts assembled from LIVE engine data (writer atlas coverage, the delivery
7
+ palette, the immune axes, the known-unknowns), so "how many enzymes / vectors / axes, how accurate" is
8
+ answered from the system itself, not invented.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from functools import lru_cache
13
+
14
+ import yaml
15
+
16
+ from pen_stack._resources import resource
17
+
18
+
19
+ @lru_cache(maxsize=1)
20
+ def metric_guide() -> dict:
21
+ return yaml.safe_load(resource("configs/metric_guide.yaml").read_text(encoding="utf-8"))
22
+
23
+
24
+ def guide_for(metric: str) -> dict | None:
25
+ """The interpretation card for a metric name (immune axis / verdict field)."""
26
+ return (metric_guide().get("metrics") or {}).get(metric)
27
+
28
+
29
+ def enrich_axes(axes: dict) -> dict:
30
+ """Attach the interpretation card (meaning/scale/bands/computed/validation/reference) to each immune axis."""
31
+ out = {}
32
+ for name, a in (axes or {}).items():
33
+ g = guide_for(name)
34
+ out[name] = {**a, "guide": g} if g else a
35
+ return out
36
+
37
+
38
+ @lru_cache(maxsize=1)
39
+ def pen_stack_facts() -> dict:
40
+ """GROUNDED facts about what PEN-STACK covers + how it computes, assembled from live engine data, never
41
+ hand-typed counts. Answers the 'meta' lane ('how many enzymes/vectors/axes, how is X computed, how accurate')."""
42
+ facts: dict = {"disclaimer": (metric_guide() or {}).get("disclaimer")}
43
+
44
+ # writer atlas coverage (enzymes / families), from the committed atlas
45
+ try:
46
+ import pandas as pd
47
+
48
+ from pen_stack import __file__ as _pkg
49
+ from pathlib import Path
50
+ atlas = pd.read_parquet(Path(_pkg).resolve().parents[0] / "atlas" / "atlas.parquet")
51
+ facts["writers"] = {
52
+ "systems": int(len(atlas)),
53
+ "families": sorted(atlas["family"].astype(str).unique().tolist()),
54
+ "n_families": int(atlas["family"].nunique()),
55
+ "measured": int((atlas["confidence"] == "measured").sum()) if "confidence" in atlas.columns else None,
56
+ "note": "writer = genome-writing enzyme system (integrases, recombinases, nucleases, transposases) on "
57
+ "common measured axes (cargo capacity, programmability, DSB-freeness, human-cell activity, "
58
+ "deliverability).",
59
+ }
60
+ except Exception:
61
+ facts["writers"] = {"note": "writer atlas requires the committed atlas.parquet"}
62
+
63
+ # delivery palette (vectors)
64
+ try:
65
+ from pen_stack.planner.delivery_vehicles import names as _vnames
66
+ from pen_stack.planner.delivery_vehicles import vehicle as _veh
67
+ vs = _vnames()
68
+ viral = [v for v in vs if (_veh(v) or {}).get("class", "").lower().startswith("vir")
69
+ or any(k in v.lower() for k in ("aav", "lenti", "adeno", "hsv", "virus"))]
70
+ facts["delivery"] = {
71
+ "n_vehicles": len(vs), "vehicles": list(vs),
72
+ "viral_like": viral, "nonviral_like": [v for v in vs if v not in viral],
73
+ "note": "each vehicle carries documented cargo capacity, integration status, cargo-form compatibility "
74
+ "(DNA/mRNA/RNP) and an ordinal immune/safety prior; the planner picks compatible vehicles.",
75
+ }
76
+ except Exception:
77
+ facts["delivery"] = {"note": "delivery palette in configs/delivery_vehicles.yaml"}
78
+
79
+ # immune-risk axes (how immunogenicity is computed) + their guides
80
+ axes = ["genotoxicity", "cd8_epitope", "innate", "preexisting_nab", "anti_peg"]
81
+ facts["immunogenicity"] = {
82
+ "n_axes": len(axes), "axes": axes,
83
+ "never_collapsed": "the per-axis profile is never fused into one score (collapsed_score is None), the "
84
+ "axes measure different mechanisms on different evidence.",
85
+ "how": {a: (guide_for(a) or {}).get("computed") for a in axes},
86
+ "validation": {a: (guide_for(a) or {}).get("validation") for a in axes},
87
+ }
88
+
89
+ # accuracy and grounding posture + the known-unknowns
90
+ try:
91
+ from pen_stack.agent.scope import load_registry
92
+ kus = [{"id": e["id"], "title": e["title"]} for e in load_registry()]
93
+ except Exception:
94
+ kus = []
95
+ facts["accuracy"] = {
96
+ "posture": "every number is a tool-computed proxy with a stated scope; proxies are labelled "
97
+ "'mechanistic/population proxy, NOT outcome-validated'; the engine ABSTAINS or flags "
98
+ "out-of-scope rather than guess; nothing is fabricated.",
99
+ "known_unknowns": kus,
100
+ "what_is_NOT_predicted": "functional titer / % of normal, in-vivo response magnitude, long-term clinical "
101
+ "durability, phenotype, these are measured clinical endpoints, never predicted.",
102
+ }
103
+
104
+ # live foundation-model oracles (which execute live + their latency class)
105
+ try:
106
+ from pen_stack.oracles.status import summary as _orsum
107
+ facts["foundation_models"] = _orsum()
108
+ except Exception:
109
+ facts["foundation_models"] = {"note": "oracle execution map in configs/oracles/execution.yaml"}
110
+ return facts