agent-bober 0.17.0 → 0.18.0
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +169 -6
- package/dist/chat/answerer.d.ts +17 -0
- package/dist/chat/answerer.d.ts.map +1 -0
- package/dist/chat/answerer.js +52 -0
- package/dist/chat/answerer.js.map +1 -0
- package/dist/chat/approval-cursor.d.ts +25 -0
- package/dist/chat/approval-cursor.d.ts.map +1 -0
- package/dist/chat/approval-cursor.js +61 -0
- package/dist/chat/approval-cursor.js.map +1 -0
- package/dist/chat/approval-reader.d.ts +11 -0
- package/dist/chat/approval-reader.d.ts.map +1 -0
- package/dist/chat/approval-reader.js +20 -0
- package/dist/chat/approval-reader.js.map +1 -0
- package/dist/chat/careful-sidecar.d.ts +15 -0
- package/dist/chat/careful-sidecar.d.ts.map +1 -0
- package/dist/chat/careful-sidecar.js +39 -0
- package/dist/chat/careful-sidecar.js.map +1 -0
- package/dist/chat/chat-session.d.ts +129 -0
- package/dist/chat/chat-session.d.ts.map +1 -0
- package/dist/chat/chat-session.js +470 -0
- package/dist/chat/chat-session.js.map +1 -0
- package/dist/chat/completion-tailer.d.ts +27 -0
- package/dist/chat/completion-tailer.d.ts.map +1 -0
- package/dist/chat/completion-tailer.js +187 -0
- package/dist/chat/completion-tailer.js.map +1 -0
- package/dist/chat/conversation-store.d.ts +22 -0
- package/dist/chat/conversation-store.d.ts.map +1 -0
- package/dist/chat/conversation-store.js +66 -0
- package/dist/chat/conversation-store.js.map +1 -0
- package/dist/chat/cursor-store.d.ts +23 -0
- package/dist/chat/cursor-store.d.ts.map +1 -0
- package/dist/chat/cursor-store.js +42 -0
- package/dist/chat/cursor-store.js.map +1 -0
- package/dist/chat/pid-sidecar.d.ts +22 -0
- package/dist/chat/pid-sidecar.d.ts.map +1 -0
- package/dist/chat/pid-sidecar.js +42 -0
- package/dist/chat/pid-sidecar.js.map +1 -0
- package/dist/chat/roster-reader.d.ts +16 -0
- package/dist/chat/roster-reader.d.ts.map +1 -0
- package/dist/chat/roster-reader.js +39 -0
- package/dist/chat/roster-reader.js.map +1 -0
- package/dist/chat/run-spawner.d.ts +71 -0
- package/dist/chat/run-spawner.d.ts.map +1 -0
- package/dist/chat/run-spawner.js +121 -0
- package/dist/chat/run-spawner.js.map +1 -0
- package/dist/chat/slash-commands.d.ts +36 -0
- package/dist/chat/slash-commands.d.ts.map +1 -0
- package/dist/chat/slash-commands.js +133 -0
- package/dist/chat/slash-commands.js.map +1 -0
- package/dist/chat/steer-cleanup.d.ts +12 -0
- package/dist/chat/steer-cleanup.d.ts.map +1 -0
- package/dist/chat/steer-cleanup.js +60 -0
- package/dist/chat/steer-cleanup.js.map +1 -0
- package/dist/chat/turn-classifier.d.ts +43 -0
- package/dist/chat/turn-classifier.d.ts.map +1 -0
- package/dist/chat/turn-classifier.js +150 -0
- package/dist/chat/turn-classifier.js.map +1 -0
- package/dist/cli/commands/blackboard.d.ts +33 -0
- package/dist/cli/commands/blackboard.d.ts.map +1 -0
- package/dist/cli/commands/blackboard.js +114 -0
- package/dist/cli/commands/blackboard.js.map +1 -0
- package/dist/cli/commands/chat.d.ts +13 -0
- package/dist/cli/commands/chat.d.ts.map +1 -0
- package/dist/cli/commands/chat.js +43 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/evolve.d.ts +18 -0
- package/dist/cli/commands/evolve.d.ts.map +1 -0
- package/dist/cli/commands/evolve.js +72 -0
- package/dist/cli/commands/evolve.js.map +1 -0
- package/dist/cli/commands/facts.d.ts +15 -0
- package/dist/cli/commands/facts.d.ts.map +1 -0
- package/dist/cli/commands/facts.js +213 -0
- package/dist/cli/commands/facts.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/medical.d.ts +24 -0
- package/dist/cli/commands/medical.d.ts.map +1 -0
- package/dist/cli/commands/medical.js +132 -0
- package/dist/cli/commands/medical.js.map +1 -0
- package/dist/cli/commands/memory.d.ts +2 -1
- package/dist/cli/commands/memory.d.ts.map +1 -1
- package/dist/cli/commands/memory.js +94 -7
- package/dist/cli/commands/memory.js.map +1 -1
- package/dist/cli/commands/plan.d.ts.map +1 -1
- package/dist/cli/commands/plan.js +29 -3
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/replay.d.ts +17 -0
- package/dist/cli/commands/replay.d.ts.map +1 -0
- package/dist/cli/commands/replay.js +261 -0
- package/dist/cli/commands/replay.js.map +1 -0
- package/dist/cli/commands/run.d.ts +9 -0
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +27 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/sprint.d.ts.map +1 -1
- package/dist/cli/commands/sprint.js +17 -3
- package/dist/cli/commands/sprint.js.map +1 -1
- package/dist/cli/commands/update.d.ts +18 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +81 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +8 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/role-providers.d.ts +1 -1
- package/dist/config/role-providers.d.ts.map +1 -1
- package/dist/config/role-providers.js +6 -1
- package/dist/config/role-providers.js.map +1 -1
- package/dist/config/schema.d.ts +467 -222
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +25 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/contracts/eval-result.d.ts +84 -84
- package/dist/contracts/spec.d.ts +28 -28
- package/dist/contracts/sprint-contract.d.ts +4 -4
- package/dist/fleet/critic-deep.d.ts +53 -0
- package/dist/fleet/critic-deep.d.ts.map +1 -0
- package/dist/fleet/critic-deep.js +199 -0
- package/dist/fleet/critic-deep.js.map +1 -0
- package/dist/fleet/decomposer-deep-constants.d.ts +4 -0
- package/dist/fleet/decomposer-deep-constants.d.ts.map +1 -0
- package/dist/fleet/decomposer-deep-constants.js +14 -0
- package/dist/fleet/decomposer-deep-constants.js.map +1 -0
- package/dist/fleet/decomposer-deep-types.d.ts +8 -0
- package/dist/fleet/decomposer-deep-types.d.ts.map +1 -0
- package/dist/fleet/decomposer-deep-types.js +9 -0
- package/dist/fleet/decomposer-deep-types.js.map +1 -0
- package/dist/fleet/decomposer-deep.d.ts +51 -0
- package/dist/fleet/decomposer-deep.d.ts.map +1 -0
- package/dist/fleet/decomposer-deep.js +261 -0
- package/dist/fleet/decomposer-deep.js.map +1 -0
- package/dist/fleet/decomposer.d.ts +22 -0
- package/dist/fleet/decomposer.d.ts.map +1 -0
- package/dist/fleet/decomposer.js +144 -0
- package/dist/fleet/decomposer.js.map +1 -0
- package/dist/fleet/index.d.ts +93 -0
- package/dist/fleet/index.d.ts.map +1 -1
- package/dist/fleet/index.js +230 -2
- package/dist/fleet/index.js.map +1 -1
- package/dist/fleet/manifest-write.d.ts +30 -0
- package/dist/fleet/manifest-write.d.ts.map +1 -0
- package/dist/fleet/manifest-write.js +90 -0
- package/dist/fleet/manifest-write.js.map +1 -0
- package/dist/fleet/manifest.d.ts +6 -6
- package/dist/fleet/shared-blackboard.d.ts +51 -0
- package/dist/fleet/shared-blackboard.d.ts.map +1 -0
- package/dist/fleet/shared-blackboard.js +79 -0
- package/dist/fleet/shared-blackboard.js.map +1 -0
- package/dist/fleet/synthesis.d.ts +16 -0
- package/dist/fleet/synthesis.d.ts.map +1 -0
- package/dist/fleet/synthesis.js +22 -0
- package/dist/fleet/synthesis.js.map +1 -0
- package/dist/fleet/tier-policy.d.ts +18 -0
- package/dist/fleet/tier-policy.d.ts.map +1 -0
- package/dist/fleet/tier-policy.js +53 -0
- package/dist/fleet/tier-policy.js.map +1 -0
- package/dist/fleet/tool-role-guard.d.ts +24 -0
- package/dist/fleet/tool-role-guard.d.ts.map +1 -0
- package/dist/fleet/tool-role-guard.js +55 -0
- package/dist/fleet/tool-role-guard.js.map +1 -0
- package/dist/incident/resolution-verify.d.ts +4 -4
- package/dist/incident/types.d.ts +8 -8
- package/dist/mcp/run-manager.d.ts +9 -1
- package/dist/mcp/run-manager.d.ts.map +1 -1
- package/dist/mcp/run-manager.js.map +1 -1
- package/dist/medical/adapters/apple-health.d.ts +32 -0
- package/dist/medical/adapters/apple-health.d.ts.map +1 -0
- package/dist/medical/adapters/apple-health.js +100 -0
- package/dist/medical/adapters/apple-health.js.map +1 -0
- package/dist/medical/audit.d.ts +31 -0
- package/dist/medical/audit.d.ts.map +1 -0
- package/dist/medical/audit.js +56 -0
- package/dist/medical/audit.js.map +1 -0
- package/dist/medical/consent.d.ts +37 -0
- package/dist/medical/consent.d.ts.map +1 -0
- package/dist/medical/consent.js +73 -0
- package/dist/medical/consent.js.map +1 -0
- package/dist/medical/disclaimer.d.ts +11 -0
- package/dist/medical/disclaimer.d.ts.map +1 -0
- package/dist/medical/disclaimer.js +21 -0
- package/dist/medical/disclaimer.js.map +1 -0
- package/dist/medical/egress.d.ts +30 -0
- package/dist/medical/egress.d.ts.map +1 -0
- package/dist/medical/egress.js +50 -0
- package/dist/medical/egress.js.map +1 -0
- package/dist/medical/engine.d.ts +73 -0
- package/dist/medical/engine.d.ts.map +1 -0
- package/dist/medical/engine.js +333 -0
- package/dist/medical/engine.js.map +1 -0
- package/dist/medical/guardrails.d.ts +47 -0
- package/dist/medical/guardrails.d.ts.map +1 -0
- package/dist/medical/guardrails.js +92 -0
- package/dist/medical/guardrails.js.map +1 -0
- package/dist/medical/health-store.d.ts +80 -0
- package/dist/medical/health-store.d.ts.map +1 -0
- package/dist/medical/health-store.js +204 -0
- package/dist/medical/health-store.js.map +1 -0
- package/dist/medical/inference.d.ts +22 -0
- package/dist/medical/inference.d.ts.map +1 -0
- package/dist/medical/inference.js +39 -0
- package/dist/medical/inference.js.map +1 -0
- package/dist/medical/ingestion.d.ts +33 -0
- package/dist/medical/ingestion.d.ts.map +1 -0
- package/dist/medical/ingestion.js +52 -0
- package/dist/medical/ingestion.js.map +1 -0
- package/dist/medical/numerics.d.ts +66 -0
- package/dist/medical/numerics.d.ts.map +1 -0
- package/dist/medical/numerics.js +216 -0
- package/dist/medical/numerics.js.map +1 -0
- package/dist/medical/red-flag.d.ts +42 -0
- package/dist/medical/red-flag.d.ts.map +1 -0
- package/dist/medical/red-flag.js +161 -0
- package/dist/medical/red-flag.js.map +1 -0
- package/dist/medical/refusal.d.ts +46 -0
- package/dist/medical/refusal.d.ts.map +1 -0
- package/dist/medical/refusal.js +131 -0
- package/dist/medical/refusal.js.map +1 -0
- package/dist/medical/retrieval/grounding-critic.d.ts +34 -0
- package/dist/medical/retrieval/grounding-critic.d.ts.map +1 -0
- package/dist/medical/retrieval/grounding-critic.js +153 -0
- package/dist/medical/retrieval/grounding-critic.js.map +1 -0
- package/dist/medical/retrieval/literature.d.ts +78 -0
- package/dist/medical/retrieval/literature.d.ts.map +1 -0
- package/dist/medical/retrieval/literature.js +292 -0
- package/dist/medical/retrieval/literature.js.map +1 -0
- package/dist/medical/retrieval/medline-source.d.ts +72 -0
- package/dist/medical/retrieval/medline-source.d.ts.map +1 -0
- package/dist/medical/retrieval/medline-source.js +120 -0
- package/dist/medical/retrieval/medline-source.js.map +1 -0
- package/dist/medical/team.d.ts +15 -0
- package/dist/medical/team.d.ts.map +1 -0
- package/dist/medical/team.js +44 -0
- package/dist/medical/team.js.map +1 -0
- package/dist/medical/types.d.ts +175 -0
- package/dist/medical/types.d.ts.map +1 -0
- package/dist/medical/types.js +8 -0
- package/dist/medical/types.js.map +1 -0
- package/dist/medical/whoop/whoop-client.d.ts +114 -0
- package/dist/medical/whoop/whoop-client.d.ts.map +1 -0
- package/dist/medical/whoop/whoop-client.js +226 -0
- package/dist/medical/whoop/whoop-client.js.map +1 -0
- package/dist/medical/whoop/whoop-sync.d.ts +25 -0
- package/dist/medical/whoop/whoop-sync.d.ts.map +1 -0
- package/dist/medical/whoop/whoop-sync.js +97 -0
- package/dist/medical/whoop/whoop-sync.js.map +1 -0
- package/dist/medical/whoop/whoop-token.d.ts +44 -0
- package/dist/medical/whoop/whoop-token.d.ts.map +1 -0
- package/dist/medical/whoop/whoop-token.js +66 -0
- package/dist/medical/whoop/whoop-token.js.map +1 -0
- package/dist/orchestrator/context-handoff.d.ts +60 -60
- package/dist/orchestrator/contract-materialization.d.ts +33 -0
- package/dist/orchestrator/contract-materialization.d.ts.map +1 -0
- package/dist/orchestrator/contract-materialization.js +113 -0
- package/dist/orchestrator/contract-materialization.js.map +1 -0
- package/dist/orchestrator/memory/distill.d.ts +4 -1
- package/dist/orchestrator/memory/distill.d.ts.map +1 -1
- package/dist/orchestrator/memory/distill.js +41 -1
- package/dist/orchestrator/memory/distill.js.map +1 -1
- package/dist/orchestrator/memory/fact-detector.d.ts +61 -0
- package/dist/orchestrator/memory/fact-detector.d.ts.map +1 -0
- package/dist/orchestrator/memory/fact-detector.js +153 -0
- package/dist/orchestrator/memory/fact-detector.js.map +1 -0
- package/dist/orchestrator/memory/fact-judge.d.ts +19 -0
- package/dist/orchestrator/memory/fact-judge.d.ts.map +1 -0
- package/dist/orchestrator/memory/fact-judge.js +121 -0
- package/dist/orchestrator/memory/fact-judge.js.map +1 -0
- package/dist/orchestrator/memory/fact-retrieve.d.ts +52 -0
- package/dist/orchestrator/memory/fact-retrieve.d.ts.map +1 -0
- package/dist/orchestrator/memory/fact-retrieve.js +117 -0
- package/dist/orchestrator/memory/fact-retrieve.js.map +1 -0
- package/dist/orchestrator/memory/hygiene.d.ts +66 -0
- package/dist/orchestrator/memory/hygiene.d.ts.map +1 -0
- package/dist/orchestrator/memory/hygiene.js +153 -0
- package/dist/orchestrator/memory/hygiene.js.map +1 -0
- package/dist/orchestrator/memory/reconcile.d.ts +47 -0
- package/dist/orchestrator/memory/reconcile.d.ts.map +1 -0
- package/dist/orchestrator/memory/reconcile.js +116 -0
- package/dist/orchestrator/memory/reconcile.js.map +1 -0
- package/dist/orchestrator/memory/retrieve.d.ts +5 -3
- package/dist/orchestrator/memory/retrieve.d.ts.map +1 -1
- package/dist/orchestrator/memory/retrieve.js +9 -6
- package/dist/orchestrator/memory/retrieve.js.map +1 -1
- package/dist/orchestrator/pipeline.d.ts +19 -3
- package/dist/orchestrator/pipeline.d.ts.map +1 -1
- package/dist/orchestrator/pipeline.js +63 -52
- package/dist/orchestrator/pipeline.js.map +1 -1
- package/dist/orchestrator/planner-agent.d.ts.map +1 -1
- package/dist/orchestrator/planner-agent.js +16 -1
- package/dist/orchestrator/planner-agent.js.map +1 -1
- package/dist/orchestrator/selfimprove/eval-guards.d.ts +51 -0
- package/dist/orchestrator/selfimprove/eval-guards.d.ts.map +1 -0
- package/dist/orchestrator/selfimprove/eval-guards.js +120 -0
- package/dist/orchestrator/selfimprove/eval-guards.js.map +1 -0
- package/dist/orchestrator/selfimprove/gepa.d.ts +74 -0
- package/dist/orchestrator/selfimprove/gepa.d.ts.map +1 -0
- package/dist/orchestrator/selfimprove/gepa.js +227 -0
- package/dist/orchestrator/selfimprove/gepa.js.map +1 -0
- package/dist/orchestrator/selfimprove/replay-harness.d.ts +65 -0
- package/dist/orchestrator/selfimprove/replay-harness.d.ts.map +1 -0
- package/dist/orchestrator/selfimprove/replay-harness.js +134 -0
- package/dist/orchestrator/selfimprove/replay-harness.js.map +1 -0
- package/dist/orchestrator/selfimprove/replay-store.d.ts +45 -0
- package/dist/orchestrator/selfimprove/replay-store.d.ts.map +1 -0
- package/dist/orchestrator/selfimprove/replay-store.js +119 -0
- package/dist/orchestrator/selfimprove/replay-store.js.map +1 -0
- package/dist/orchestrator/selfimprove/replay-types.d.ts +32 -0
- package/dist/orchestrator/selfimprove/replay-types.d.ts.map +1 -0
- package/dist/orchestrator/selfimprove/replay-types.js +15 -0
- package/dist/orchestrator/selfimprove/replay-types.js.map +1 -0
- package/dist/orchestrator/workflow/engine.d.ts +3 -1
- package/dist/orchestrator/workflow/engine.d.ts.map +1 -1
- package/dist/orchestrator/workflow/selector.d.ts +18 -0
- package/dist/orchestrator/workflow/selector.d.ts.map +1 -1
- package/dist/orchestrator/workflow/selector.js +41 -0
- package/dist/orchestrator/workflow/selector.js.map +1 -1
- package/dist/orchestrator/workflow/ts-engine.d.ts +3 -1
- package/dist/orchestrator/workflow/ts-engine.d.ts.map +1 -1
- package/dist/orchestrator/workflow/ts-engine.js +2 -2
- package/dist/orchestrator/workflow/ts-engine.js.map +1 -1
- package/dist/orchestrator/workflow/workflow-engine.d.ts +3 -1
- package/dist/orchestrator/workflow/workflow-engine.d.ts.map +1 -1
- package/dist/orchestrator/workflow/workflow-engine.js +3 -3
- package/dist/orchestrator/workflow/workflow-engine.js.map +1 -1
- package/dist/state/facts.d.ts +110 -0
- package/dist/state/facts.d.ts.map +1 -0
- package/dist/state/facts.js +208 -0
- package/dist/state/facts.js.map +1 -0
- package/dist/state/guidance.d.ts +37 -0
- package/dist/state/guidance.d.ts.map +1 -0
- package/dist/state/guidance.js +144 -0
- package/dist/state/guidance.js.map +1 -0
- package/dist/state/history.d.ts +2 -2
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +1 -1
- package/dist/state/index.js.map +1 -1
- package/dist/state/memory.d.ts +43 -11
- package/dist/state/memory.d.ts.map +1 -1
- package/dist/state/memory.js +93 -15
- package/dist/state/memory.js.map +1 -1
- package/dist/state/pause.d.ts +47 -0
- package/dist/state/pause.d.ts.map +1 -0
- package/dist/state/pause.js +146 -0
- package/dist/state/pause.js.map +1 -0
- package/dist/state/sprint-state.d.ts +8 -0
- package/dist/state/sprint-state.d.ts.map +1 -1
- package/dist/state/sprint-state.js +32 -1
- package/dist/state/sprint-state.js.map +1 -1
- package/dist/teams/registry.d.ts +16 -0
- package/dist/teams/registry.d.ts.map +1 -0
- package/dist/teams/registry.js +53 -0
- package/dist/teams/registry.js.map +1 -0
- package/dist/teams/types.d.ts +25 -0
- package/dist/teams/types.d.ts.map +1 -0
- package/dist/teams/types.js +2 -0
- package/dist/teams/types.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MedicalGuardrails — real GuardrailSet wrapping RedFlagDetector + RefusalDetector
|
|
3
|
+
* (Phase 6, Sprint 3 + Sprint 1 of spec-20260617-medical-whoop-guardrails).
|
|
4
|
+
*
|
|
5
|
+
* evaluate(prompt, ctx) runs the red-flag detector first; a match returns a
|
|
6
|
+
* short-circuit verdict with a canned 911/988 escalation. Then runs the refusal
|
|
7
|
+
* detector; a match returns a refuse verdict with a fixed canned decline message.
|
|
8
|
+
* Non-emergency, non-refusal prompts return { kind: "allow" }.
|
|
9
|
+
*
|
|
10
|
+
* NO network. NO LLM import. No src/providers import.
|
|
11
|
+
*/
|
|
12
|
+
import type { GuardrailContext, GuardrailSet, GuardrailVerdict } from "./types.js";
|
|
13
|
+
import { RedFlagDetector } from "./red-flag.js";
|
|
14
|
+
import { RefusalDetector } from "./refusal.js";
|
|
15
|
+
/** Versioned ruleset identifier recorded in the audit log. */
|
|
16
|
+
export declare const GUARDRAIL_RULESET_VERSION = "guardrail-2026.06.16";
|
|
17
|
+
/**
|
|
18
|
+
* Real medical GuardrailSet implementation.
|
|
19
|
+
*
|
|
20
|
+
* Replaces the allow-only stub in team.ts:buildMedicalGuardrails (Sprint 1–2).
|
|
21
|
+
* team.ts now delegates to this class; engine.ts defaults to it.
|
|
22
|
+
*/
|
|
23
|
+
export declare class MedicalGuardrails implements GuardrailSet {
|
|
24
|
+
readonly rulesetVersion = "guardrail-2026.06.16";
|
|
25
|
+
/**
|
|
26
|
+
* Expose the detector so the engine can read patternsetVersion for the audit entry.
|
|
27
|
+
* Read-only; tests may introspect but must not replace at runtime.
|
|
28
|
+
*/
|
|
29
|
+
readonly detector: RedFlagDetector;
|
|
30
|
+
/**
|
|
31
|
+
* Refusal detector for non-emergency content-policy refusals
|
|
32
|
+
* (prescription / specific-dosing / individualized-treatment-plan).
|
|
33
|
+
*/
|
|
34
|
+
readonly refusal: RefusalDetector;
|
|
35
|
+
/**
|
|
36
|
+
* Evaluate the prompt against the medical guardrail ruleset.
|
|
37
|
+
*
|
|
38
|
+
* @throws {Error} if prompt is empty or whitespace-only (sc-3-8).
|
|
39
|
+
* @returns GuardrailVerdict — short-circuit for red flags, allow for benign prompts.
|
|
40
|
+
*/
|
|
41
|
+
evaluate(prompt: string, _ctx: GuardrailContext): GuardrailVerdict;
|
|
42
|
+
/** Expose red-flag PATTERNSET_VERSION so callers don't need a separate import. */
|
|
43
|
+
get patternsetVersion(): string;
|
|
44
|
+
/** Expose refusal REFUSAL_PATTERNSET_VERSION for the engine's refuse audit entry. */
|
|
45
|
+
get refusalPatternsetVersion(): string;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=guardrails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../src/medical/guardrails.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,eAAe,EAAsB,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAE,eAAe,EAA+C,MAAM,cAAc,CAAC;AAI5F,8DAA8D;AAC9D,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAqChE;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IACpD,QAAQ,CAAC,cAAc,0BAA6B;IAEpD;;;OAGG;IACH,QAAQ,CAAC,QAAQ,kBAAyB;IAE1C;;;OAGG;IACH,QAAQ,CAAC,OAAO,kBAAyB;IAEzC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,gBAAgB;IA6BlE,kFAAkF;IAClF,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,qFAAqF;IACrF,IAAI,wBAAwB,IAAI,MAAM,CAErC;CACF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { RedFlagDetector, PATTERNSET_VERSION } from "./red-flag.js";
|
|
2
|
+
import { RefusalDetector, REFUSAL_PATTERNSET_VERSION, REFUSAL_REASONS } from "./refusal.js";
|
|
3
|
+
// ── Version constant ─────────────────────────────────────────────────
|
|
4
|
+
/** Versioned ruleset identifier recorded in the audit log. */
|
|
5
|
+
export const GUARDRAIL_RULESET_VERSION = "guardrail-2026.06.16";
|
|
6
|
+
// ── Canned escalation strings (NEVER model-generated) ───────────────
|
|
7
|
+
/**
|
|
8
|
+
* Fixed 911 escalation text.
|
|
9
|
+
* Returned verbatim; never passed through an LLM.
|
|
10
|
+
*/
|
|
11
|
+
const ESCALATION_911 = "This may be a medical emergency. Call 911 (or your local emergency number) now.";
|
|
12
|
+
/**
|
|
13
|
+
* Fixed 988 escalation text.
|
|
14
|
+
* Returned verbatim; never passed through an LLM.
|
|
15
|
+
*/
|
|
16
|
+
const ESCALATION_988 = "If you are in crisis, call or text 988 (US Suicide & Crisis Lifeline) now, " +
|
|
17
|
+
"or 911 if you are in immediate danger.";
|
|
18
|
+
/** Maps a red-flag category to its fixed escalation string. */
|
|
19
|
+
function escalationFor(category) {
|
|
20
|
+
switch (category) {
|
|
21
|
+
case "cardiac":
|
|
22
|
+
case "stroke":
|
|
23
|
+
case "anaphylaxis":
|
|
24
|
+
return ESCALATION_911;
|
|
25
|
+
case "self-harm":
|
|
26
|
+
case "overdose":
|
|
27
|
+
return ESCALATION_988;
|
|
28
|
+
default:
|
|
29
|
+
// Unreachable for 'none'; safe fallback is 911.
|
|
30
|
+
return ESCALATION_911;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// ── MedicalGuardrails ────────────────────────────────────────────────
|
|
34
|
+
/**
|
|
35
|
+
* Real medical GuardrailSet implementation.
|
|
36
|
+
*
|
|
37
|
+
* Replaces the allow-only stub in team.ts:buildMedicalGuardrails (Sprint 1–2).
|
|
38
|
+
* team.ts now delegates to this class; engine.ts defaults to it.
|
|
39
|
+
*/
|
|
40
|
+
export class MedicalGuardrails {
|
|
41
|
+
rulesetVersion = GUARDRAIL_RULESET_VERSION;
|
|
42
|
+
/**
|
|
43
|
+
* Expose the detector so the engine can read patternsetVersion for the audit entry.
|
|
44
|
+
* Read-only; tests may introspect but must not replace at runtime.
|
|
45
|
+
*/
|
|
46
|
+
detector = new RedFlagDetector();
|
|
47
|
+
/**
|
|
48
|
+
* Refusal detector for non-emergency content-policy refusals
|
|
49
|
+
* (prescription / specific-dosing / individualized-treatment-plan).
|
|
50
|
+
*/
|
|
51
|
+
refusal = new RefusalDetector();
|
|
52
|
+
/**
|
|
53
|
+
* Evaluate the prompt against the medical guardrail ruleset.
|
|
54
|
+
*
|
|
55
|
+
* @throws {Error} if prompt is empty or whitespace-only (sc-3-8).
|
|
56
|
+
* @returns GuardrailVerdict — short-circuit for red flags, allow for benign prompts.
|
|
57
|
+
*/
|
|
58
|
+
evaluate(prompt, _ctx) {
|
|
59
|
+
if (prompt.trim().length === 0) {
|
|
60
|
+
throw new Error("GuardrailSet.evaluate: prompt must not be empty or whitespace-only");
|
|
61
|
+
}
|
|
62
|
+
const match = this.detector.detect(prompt);
|
|
63
|
+
if (match.category !== "none") {
|
|
64
|
+
return {
|
|
65
|
+
kind: "short-circuit",
|
|
66
|
+
rule: match.ruleId ?? match.category,
|
|
67
|
+
cannedResponse: escalationFor(match.category),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// Gate 2b: Content-policy refusal (prescription / dosing / treatment-plan).
|
|
71
|
+
// Only reached when red-flag is 'none' — emergency precedence is guaranteed by the
|
|
72
|
+
// early return above.
|
|
73
|
+
const r = this.refusal.detect(prompt);
|
|
74
|
+
if (r.category !== "none") {
|
|
75
|
+
return {
|
|
76
|
+
kind: "refuse",
|
|
77
|
+
rule: r.ruleId ?? r.category,
|
|
78
|
+
reason: REFUSAL_REASONS[r.category],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { kind: "allow" };
|
|
82
|
+
}
|
|
83
|
+
/** Expose red-flag PATTERNSET_VERSION so callers don't need a separate import. */
|
|
84
|
+
get patternsetVersion() {
|
|
85
|
+
return PATTERNSET_VERSION;
|
|
86
|
+
}
|
|
87
|
+
/** Expose refusal REFUSAL_PATTERNSET_VERSION for the engine's refuse audit entry. */
|
|
88
|
+
get refusalPatternsetVersion() {
|
|
89
|
+
return REFUSAL_PATTERNSET_VERSION;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=guardrails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guardrails.js","sourceRoot":"","sources":["../../src/medical/guardrails.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE5F,wEAAwE;AAExE,8DAA8D;AAC9D,MAAM,CAAC,MAAM,yBAAyB,GAAG,sBAAsB,CAAC;AAEhE,uEAAuE;AAEvE;;;GAGG;AACH,MAAM,cAAc,GAClB,iFAAiF,CAAC;AAEpF;;;GAGG;AACH,MAAM,cAAc,GAClB,6EAA6E;IAC7E,wCAAwC,CAAC;AAE3C,+DAA+D;AAC/D,SAAS,aAAa,CAAC,QAAyB;IAC9C,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,SAAS,CAAC;QACf,KAAK,QAAQ,CAAC;QACd,KAAK,aAAa;YAChB,OAAO,cAAc,CAAC;QACxB,KAAK,WAAW,CAAC;QACjB,KAAK,UAAU;YACb,OAAO,cAAc,CAAC;QACxB;YACE,gDAAgD;YAChD,OAAO,cAAc,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,wEAAwE;AAExE;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IACnB,cAAc,GAAG,yBAAyB,CAAC;IAEpD;;;OAGG;IACM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IAE1C;;;OAGG;IACM,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;IAEzC;;;;;OAKG;IACH,QAAQ,CAAC,MAAc,EAAE,IAAsB;QAC7C,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAC9B,OAAO;gBACL,IAAI,EAAE,eAAe;gBACrB,IAAI,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ;gBACpC,cAAc,EAAE,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC;aAC9C,CAAC;QACJ,CAAC;QAED,4EAA4E;QAC5E,mFAAmF;QACnF,sBAAsB;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAC1B,OAAO;gBACL,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ;gBAC5B,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC;aACpC,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,kFAAkF;IAClF,IAAI,iBAAiB;QACnB,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,qFAAqF;IACrF,IAAI,wBAAwB;QAC1B,OAAO,0BAA0B,CAAC;IACpC,CAAC;CACF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HealthDataStore — SQLite-backed health observation store (Phase 6, Sprint 4).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors FactStore (src/state/facts.ts) exactly:
|
|
5
|
+
* - better-sqlite3 SYNC (no await anywhere)
|
|
6
|
+
* - Single generic `health_observations` events table + `lab_results` + `kv_store`
|
|
7
|
+
* - CREATE TABLE IF NOT EXISTS in constructor
|
|
8
|
+
* - INSERT OR IGNORE on deterministic SHA-256 id
|
|
9
|
+
* - upsertObservations returns NEW-row count only (sum of info.changes)
|
|
10
|
+
*
|
|
11
|
+
* PURE: Never calls Date.now() or new Date() — every timestamp is an injected parameter.
|
|
12
|
+
* ADR-4: Generic single-table pattern, deterministic id = SHA-256(metric|tStart|source|value).
|
|
13
|
+
*
|
|
14
|
+
* bober: in-memory or file-backed via better-sqlite3 (synchronous); swap for node:sqlite
|
|
15
|
+
* when engines.node is raised to >=22.5.
|
|
16
|
+
*/
|
|
17
|
+
import type { HealthObservation, LabResult, Baseline } from "./types.js";
|
|
18
|
+
/**
|
|
19
|
+
* Derive a deterministic 16-char hex id for a health observation.
|
|
20
|
+
* Mirrors factId at src/state/facts.ts:58-69.
|
|
21
|
+
* Signature: metric|tStart|source|value (ADR-4 contract line 73).
|
|
22
|
+
* Identical inputs always produce the same id — no wall-clock dependency.
|
|
23
|
+
*/
|
|
24
|
+
export declare function observationId(metric: string, tStart: string, source: string, value: number): string;
|
|
25
|
+
/**
|
|
26
|
+
* Derive a deterministic 16-char hex id for a lab result.
|
|
27
|
+
* Signature: biomarker|collectedAtIso|value.
|
|
28
|
+
*/
|
|
29
|
+
export declare function labResultId(biomarker: string, collectedAtIso: string, value: number): string;
|
|
30
|
+
/**
|
|
31
|
+
* SQLite-backed health observation store.
|
|
32
|
+
*
|
|
33
|
+
* Tables:
|
|
34
|
+
* health_observations — generic single-table for metric time series (ADR-4)
|
|
35
|
+
* lab_results — laboratory biomarker results with reference ranges
|
|
36
|
+
* kv_store — key/value backing for getBaseline/putBaseline + getPreference
|
|
37
|
+
*
|
|
38
|
+
* All methods are SYNCHRONOUS (better-sqlite3 API). No await anywhere.
|
|
39
|
+
*/
|
|
40
|
+
export declare class HealthDataStore {
|
|
41
|
+
private db;
|
|
42
|
+
constructor(dbPath: string);
|
|
43
|
+
/**
|
|
44
|
+
* Insert observations, ignoring duplicates (INSERT OR IGNORE on the deterministic id).
|
|
45
|
+
* Returns the count of NEW rows only (0 for duplicates, 1 per genuinely new row).
|
|
46
|
+
* ADR-4: id = SHA-256(metric|tStart|source|value).slice(0,16).
|
|
47
|
+
*/
|
|
48
|
+
upsertObservations(rows: HealthObservation[]): number;
|
|
49
|
+
/**
|
|
50
|
+
* Return observations for a metric within [fromIso, toIso], ordered by t_start ASC.
|
|
51
|
+
* ISO-8601 strings sort lexicographically == chronologically.
|
|
52
|
+
*/
|
|
53
|
+
getObservations(metric: string, fromIso: string, toIso: string): HealthObservation[];
|
|
54
|
+
/**
|
|
55
|
+
* Return all lab results for a biomarker, ordered by collected_at ASC.
|
|
56
|
+
*/
|
|
57
|
+
getLabSeries(biomarker: string): LabResult[];
|
|
58
|
+
/**
|
|
59
|
+
* Insert a lab result, ignoring duplicates.
|
|
60
|
+
* Returns the count of NEW rows inserted (0 or 1).
|
|
61
|
+
*/
|
|
62
|
+
upsertLabResult(result: LabResult): number;
|
|
63
|
+
/**
|
|
64
|
+
* Read a stored baseline for a metric from the kv_store.
|
|
65
|
+
* Returns undefined if no baseline has been stored.
|
|
66
|
+
*/
|
|
67
|
+
getBaseline(metric: string): Baseline | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Persist a baseline value to the kv_store (INSERT OR REPLACE).
|
|
70
|
+
*/
|
|
71
|
+
putBaseline(b: Baseline): void;
|
|
72
|
+
/**
|
|
73
|
+
* Read a preference string from the kv_store.
|
|
74
|
+
* Returns undefined if the key is not set.
|
|
75
|
+
*/
|
|
76
|
+
getPreference(key: string): string | undefined;
|
|
77
|
+
/** Close the underlying database connection. */
|
|
78
|
+
close(): void;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=health-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-store.d.ts","sourceRoot":"","sources":["../../src/medical/health-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIzE;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,MAAM,CAKR;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK5F;AAoDD;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAe;gBAEb,MAAM,EAAE,MAAM;IAgC1B;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,MAAM;IAsBrD;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,EAAE;IAYpF;;OAEG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE;IAY5C;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM;IAoB1C;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAQjD;;OAEG;IACH,WAAW,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI;IAM9B;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAO9C,gDAAgD;IAChD,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HealthDataStore — SQLite-backed health observation store (Phase 6, Sprint 4).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors FactStore (src/state/facts.ts) exactly:
|
|
5
|
+
* - better-sqlite3 SYNC (no await anywhere)
|
|
6
|
+
* - Single generic `health_observations` events table + `lab_results` + `kv_store`
|
|
7
|
+
* - CREATE TABLE IF NOT EXISTS in constructor
|
|
8
|
+
* - INSERT OR IGNORE on deterministic SHA-256 id
|
|
9
|
+
* - upsertObservations returns NEW-row count only (sum of info.changes)
|
|
10
|
+
*
|
|
11
|
+
* PURE: Never calls Date.now() or new Date() — every timestamp is an injected parameter.
|
|
12
|
+
* ADR-4: Generic single-table pattern, deterministic id = SHA-256(metric|tStart|source|value).
|
|
13
|
+
*
|
|
14
|
+
* bober: in-memory or file-backed via better-sqlite3 (synchronous); swap for node:sqlite
|
|
15
|
+
* when engines.node is raised to >=22.5.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import Database from "better-sqlite3";
|
|
19
|
+
// ── Deterministic id helper ───────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Derive a deterministic 16-char hex id for a health observation.
|
|
22
|
+
* Mirrors factId at src/state/facts.ts:58-69.
|
|
23
|
+
* Signature: metric|tStart|source|value (ADR-4 contract line 73).
|
|
24
|
+
* Identical inputs always produce the same id — no wall-clock dependency.
|
|
25
|
+
*/
|
|
26
|
+
export function observationId(metric, tStart, source, value) {
|
|
27
|
+
return createHash("sha256")
|
|
28
|
+
.update(`${metric}|${tStart}|${source}|${value}`)
|
|
29
|
+
.digest("hex")
|
|
30
|
+
.slice(0, 16);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derive a deterministic 16-char hex id for a lab result.
|
|
34
|
+
* Signature: biomarker|collectedAtIso|value.
|
|
35
|
+
*/
|
|
36
|
+
export function labResultId(biomarker, collectedAtIso, value) {
|
|
37
|
+
return createHash("sha256")
|
|
38
|
+
.update(`${biomarker}|${collectedAtIso}|${value}`)
|
|
39
|
+
.digest("hex")
|
|
40
|
+
.slice(0, 16);
|
|
41
|
+
}
|
|
42
|
+
// ── Row mappers (snake_case → camelCase) ──────────────────────────────
|
|
43
|
+
function rowToObservation(row) {
|
|
44
|
+
return {
|
|
45
|
+
id: row.id,
|
|
46
|
+
metric: row.metric,
|
|
47
|
+
value: row.value,
|
|
48
|
+
unit: row.unit,
|
|
49
|
+
tStart: row.t_start,
|
|
50
|
+
tEnd: row.t_end ?? undefined,
|
|
51
|
+
source: row.source,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function rowToLabResult(row) {
|
|
55
|
+
return {
|
|
56
|
+
id: row.id,
|
|
57
|
+
biomarker: row.biomarker,
|
|
58
|
+
value: row.value,
|
|
59
|
+
unit: row.unit,
|
|
60
|
+
collectedAtIso: row.collected_at,
|
|
61
|
+
referenceLow: row.ref_low ?? undefined,
|
|
62
|
+
referenceHigh: row.ref_high ?? undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// ── HealthDataStore ───────────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* SQLite-backed health observation store.
|
|
68
|
+
*
|
|
69
|
+
* Tables:
|
|
70
|
+
* health_observations — generic single-table for metric time series (ADR-4)
|
|
71
|
+
* lab_results — laboratory biomarker results with reference ranges
|
|
72
|
+
* kv_store — key/value backing for getBaseline/putBaseline + getPreference
|
|
73
|
+
*
|
|
74
|
+
* All methods are SYNCHRONOUS (better-sqlite3 API). No await anywhere.
|
|
75
|
+
*/
|
|
76
|
+
export class HealthDataStore {
|
|
77
|
+
db;
|
|
78
|
+
constructor(dbPath) {
|
|
79
|
+
this.db = new Database(dbPath);
|
|
80
|
+
this.db.exec(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS health_observations (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
metric TEXT NOT NULL,
|
|
84
|
+
value REAL NOT NULL,
|
|
85
|
+
unit TEXT NOT NULL,
|
|
86
|
+
t_start TEXT NOT NULL,
|
|
87
|
+
t_end TEXT,
|
|
88
|
+
source TEXT NOT NULL
|
|
89
|
+
);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_obs_metric ON health_observations(metric, t_start);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS lab_results (
|
|
93
|
+
id TEXT PRIMARY KEY,
|
|
94
|
+
biomarker TEXT NOT NULL,
|
|
95
|
+
value REAL NOT NULL,
|
|
96
|
+
unit TEXT NOT NULL,
|
|
97
|
+
collected_at TEXT NOT NULL,
|
|
98
|
+
ref_low REAL,
|
|
99
|
+
ref_high REAL
|
|
100
|
+
);
|
|
101
|
+
CREATE INDEX IF NOT EXISTS idx_lab_biomarker ON lab_results(biomarker, collected_at);
|
|
102
|
+
|
|
103
|
+
CREATE TABLE IF NOT EXISTS kv_store (
|
|
104
|
+
k TEXT PRIMARY KEY,
|
|
105
|
+
v TEXT NOT NULL
|
|
106
|
+
);
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Insert observations, ignoring duplicates (INSERT OR IGNORE on the deterministic id).
|
|
111
|
+
* Returns the count of NEW rows only (0 for duplicates, 1 per genuinely new row).
|
|
112
|
+
* ADR-4: id = SHA-256(metric|tStart|source|value).slice(0,16).
|
|
113
|
+
*/
|
|
114
|
+
upsertObservations(rows) {
|
|
115
|
+
const stmt = this.db.prepare(`INSERT OR IGNORE INTO health_observations
|
|
116
|
+
(id, metric, value, unit, t_start, t_end, source)
|
|
117
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
118
|
+
// better-sqlite3 .transaction returns the fn's return value synchronously.
|
|
119
|
+
const insertAll = this.db.transaction((obs) => {
|
|
120
|
+
let inserted = 0;
|
|
121
|
+
for (const o of obs) {
|
|
122
|
+
const id = o.id ?? observationId(o.metric, o.tStart, o.source, o.value);
|
|
123
|
+
const info = stmt.run(id, o.metric, o.value, o.unit, o.tStart, o.tEnd ?? null, o.source);
|
|
124
|
+
// INSERT OR IGNORE => info.changes is 0 for a dup, 1 for a new row.
|
|
125
|
+
inserted += info.changes;
|
|
126
|
+
}
|
|
127
|
+
return inserted;
|
|
128
|
+
});
|
|
129
|
+
return insertAll(rows);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Return observations for a metric within [fromIso, toIso], ordered by t_start ASC.
|
|
133
|
+
* ISO-8601 strings sort lexicographically == chronologically.
|
|
134
|
+
*/
|
|
135
|
+
getObservations(metric, fromIso, toIso) {
|
|
136
|
+
const rows = this.db
|
|
137
|
+
.prepare(`SELECT id, metric, value, unit, t_start, t_end, source
|
|
138
|
+
FROM health_observations
|
|
139
|
+
WHERE metric = ? AND t_start >= ? AND t_start <= ?
|
|
140
|
+
ORDER BY t_start ASC`)
|
|
141
|
+
.all(metric, fromIso, toIso);
|
|
142
|
+
return rows.map(rowToObservation);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Return all lab results for a biomarker, ordered by collected_at ASC.
|
|
146
|
+
*/
|
|
147
|
+
getLabSeries(biomarker) {
|
|
148
|
+
const rows = this.db
|
|
149
|
+
.prepare(`SELECT id, biomarker, value, unit, collected_at, ref_low, ref_high
|
|
150
|
+
FROM lab_results
|
|
151
|
+
WHERE biomarker = ?
|
|
152
|
+
ORDER BY collected_at ASC`)
|
|
153
|
+
.all(biomarker);
|
|
154
|
+
return rows.map(rowToLabResult);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Insert a lab result, ignoring duplicates.
|
|
158
|
+
* Returns the count of NEW rows inserted (0 or 1).
|
|
159
|
+
*/
|
|
160
|
+
upsertLabResult(result) {
|
|
161
|
+
const id = result.id ?? labResultId(result.biomarker, result.collectedAtIso, result.value);
|
|
162
|
+
const info = this.db
|
|
163
|
+
.prepare(`INSERT OR IGNORE INTO lab_results
|
|
164
|
+
(id, biomarker, value, unit, collected_at, ref_low, ref_high)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
166
|
+
.run(id, result.biomarker, result.value, result.unit, result.collectedAtIso, result.referenceLow ?? null, result.referenceHigh ?? null);
|
|
167
|
+
return info.changes;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Read a stored baseline for a metric from the kv_store.
|
|
171
|
+
* Returns undefined if no baseline has been stored.
|
|
172
|
+
*/
|
|
173
|
+
getBaseline(metric) {
|
|
174
|
+
const row = this.db
|
|
175
|
+
.prepare(`SELECT v FROM kv_store WHERE k = ?`)
|
|
176
|
+
.get(`baseline:${metric}`);
|
|
177
|
+
if (!row)
|
|
178
|
+
return undefined;
|
|
179
|
+
return JSON.parse(row.v);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Persist a baseline value to the kv_store (INSERT OR REPLACE).
|
|
183
|
+
*/
|
|
184
|
+
putBaseline(b) {
|
|
185
|
+
this.db
|
|
186
|
+
.prepare(`INSERT OR REPLACE INTO kv_store (k, v) VALUES (?, ?)`)
|
|
187
|
+
.run(`baseline:${b.metric}`, JSON.stringify(b));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Read a preference string from the kv_store.
|
|
191
|
+
* Returns undefined if the key is not set.
|
|
192
|
+
*/
|
|
193
|
+
getPreference(key) {
|
|
194
|
+
const row = this.db
|
|
195
|
+
.prepare(`SELECT v FROM kv_store WHERE k = ?`)
|
|
196
|
+
.get(`pref:${key}`);
|
|
197
|
+
return row?.v;
|
|
198
|
+
}
|
|
199
|
+
/** Close the underlying database connection. */
|
|
200
|
+
close() {
|
|
201
|
+
this.db.close();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=health-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-store.js","sourceRoot":"","sources":["../../src/medical/health-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Medical inference resolver — local-default fail-closed; cloud only behind cloud-inference (Sprint 3). */
|
|
2
|
+
import type { BoberConfig } from "../config/schema.js";
|
|
3
|
+
import type { LLMClient } from "../providers/types.js";
|
|
4
|
+
import type { EgressGuard } from "./egress.js";
|
|
5
|
+
import { createClient } from "../providers/factory.js";
|
|
6
|
+
/** Injectable factory seam so tests can spy without real network. Defaults to the real createClient. */
|
|
7
|
+
export type ClientFactory = typeof createClient;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the synthesis/critic LLMClient + model from config, gated by cloud-inference.
|
|
10
|
+
*
|
|
11
|
+
* - No inference config => exact local default (back-compat, byte-identical to engine.ts:402).
|
|
12
|
+
* - inference points at the local provider/endpoint => used as-is (non-egressing).
|
|
13
|
+
* - inference points at a CLOUD provider/endpoint AND cloud-inference is OFF => FAIL CLOSED to local.
|
|
14
|
+
* - cloud config AND cloud-inference is ON => the configured cloud client/model is built.
|
|
15
|
+
*
|
|
16
|
+
* The local-vs-cloud decision lives ONLY here; createClient is the sole client-construction seam.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildMedicalInferenceClient(config: BoberConfig, egress: EgressGuard, factory?: ClientFactory): {
|
|
19
|
+
client: LLMClient;
|
|
20
|
+
model: string;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=inference.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference.d.ts","sourceRoot":"","sources":["../../src/medical/inference.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAWvD,wGAAwG;AACxG,MAAM,MAAM,aAAa,GAAG,OAAO,YAAY,CAAC;AAIhD;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,aAA4B,GACpC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAqBtC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createClient } from "../providers/factory.js";
|
|
2
|
+
// ── Local default (non-egressing) ────────────────────────────────────
|
|
3
|
+
/** The local Ollama default — provider openai-compat, localhost endpoint, llama3. Treated as non-egressing. */
|
|
4
|
+
const LOCAL = {
|
|
5
|
+
provider: "openai-compat",
|
|
6
|
+
endpoint: "http://localhost:11434/v1",
|
|
7
|
+
model: "llama3",
|
|
8
|
+
};
|
|
9
|
+
// ── buildMedicalInferenceClient ──────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the synthesis/critic LLMClient + model from config, gated by cloud-inference.
|
|
12
|
+
*
|
|
13
|
+
* - No inference config => exact local default (back-compat, byte-identical to engine.ts:402).
|
|
14
|
+
* - inference points at the local provider/endpoint => used as-is (non-egressing).
|
|
15
|
+
* - inference points at a CLOUD provider/endpoint AND cloud-inference is OFF => FAIL CLOSED to local.
|
|
16
|
+
* - cloud config AND cloud-inference is ON => the configured cloud client/model is built.
|
|
17
|
+
*
|
|
18
|
+
* The local-vs-cloud decision lives ONLY here; createClient is the sole client-construction seam.
|
|
19
|
+
*/
|
|
20
|
+
export function buildMedicalInferenceClient(config, egress, factory = createClient) {
|
|
21
|
+
const inf = config.medical?.inference;
|
|
22
|
+
const wantProvider = inf?.provider ?? LOCAL.provider;
|
|
23
|
+
const wantEndpoint = inf?.endpoint ?? LOCAL.endpoint;
|
|
24
|
+
// "Local" = openai-compat against a localhost endpoint. Anything else is treated as cloud.
|
|
25
|
+
const isLocal = wantProvider === LOCAL.provider && wantEndpoint.includes("localhost");
|
|
26
|
+
// FAIL CLOSED: cloud config requested but the cloud-inference axis is not opted in.
|
|
27
|
+
if (!isLocal && !egress.isAllowed("cloud-inference")) {
|
|
28
|
+
return {
|
|
29
|
+
client: factory(LOCAL.provider, LOCAL.endpoint, undefined, LOCAL.model),
|
|
30
|
+
model: LOCAL.model,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Either local, or cloud-with-opt-in: honour the (possibly overridden) config.
|
|
34
|
+
const provider = inf?.provider ?? LOCAL.provider;
|
|
35
|
+
const endpoint = inf?.endpoint ?? LOCAL.endpoint;
|
|
36
|
+
const model = inf?.model ?? LOCAL.model;
|
|
37
|
+
return { client: factory(provider, endpoint, undefined, model), model };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=inference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference.js","sourceRoot":"","sources":["../../src/medical/inference.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,wEAAwE;AAExE,+GAA+G;AAC/G,MAAM,KAAK,GAAG;IACZ,QAAQ,EAAE,eAAe;IACzB,QAAQ,EAAE,2BAA2B;IACrC,KAAK,EAAE,QAAQ;CACP,CAAC;AAKX,wEAAwE;AAExE;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAAmB,EACnB,MAAmB,EACnB,UAAyB,YAAY;IAErC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC;IAEtC,MAAM,YAAY,GAAG,GAAG,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;IACrD,MAAM,YAAY,GAAG,GAAG,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;IACrD,2FAA2F;IAC3F,MAAM,OAAO,GAAG,YAAY,KAAK,KAAK,CAAC,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAEtF,oFAAoF;IACpF,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACrD,OAAO;YACL,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC;YACvE,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC;IACJ,CAAC;IAED,+EAA+E;IAC/E,MAAM,QAAQ,GAAG,GAAG,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;IACjD,MAAM,QAAQ,GAAG,GAAG,EAAE,QAAQ,IAAI,KAAK,CAAC,QAAQ,CAAC;IACjD,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC;IACxC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** IngestionNormalizer + StoreObservationSink — streaming health import (Phase 6, Sprint 5). */
|
|
2
|
+
import type { HealthDataStore } from "./health-store.js";
|
|
3
|
+
import type { HealthObservation, IngestionAdapter, IngestionResult, LabResult, ObservationSink } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* ObservationSink implementation that batches writes into HealthDataStore.
|
|
6
|
+
* Accumulates newRows across all writeBatch calls for the final IngestionResult.
|
|
7
|
+
* writeBatch is async so the adapter can await it and apply backpressure.
|
|
8
|
+
*/
|
|
9
|
+
export declare class StoreObservationSink implements ObservationSink {
|
|
10
|
+
private readonly store;
|
|
11
|
+
/** Accumulates the total count of NEW rows inserted across all batches. */
|
|
12
|
+
newRows: number;
|
|
13
|
+
constructor(store: HealthDataStore);
|
|
14
|
+
writeBatch(obs: HealthObservation[], labs: LabResult[]): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Registry of IngestionAdapters.
|
|
18
|
+
* register() adds adapters; importFile() dispatches to the first matching one.
|
|
19
|
+
* Throws a clear Error naming the file if no adapter canHandle it (sc-5-7).
|
|
20
|
+
*/
|
|
21
|
+
export declare class IngestionNormalizer {
|
|
22
|
+
private readonly adapters;
|
|
23
|
+
private readonly sink;
|
|
24
|
+
constructor(sink: ObservationSink);
|
|
25
|
+
/** Add an adapter to the registry. */
|
|
26
|
+
register(adapter: IngestionAdapter): void;
|
|
27
|
+
/**
|
|
28
|
+
* Import a file using the first matching adapter.
|
|
29
|
+
* Throws if no adapter canHandle the file (message contains the file path).
|
|
30
|
+
*/
|
|
31
|
+
importFile(filePath: string): Promise<IngestionResult>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=ingestion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingestion.d.ts","sourceRoot":"","sources":["../../src/medical/ingestion.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAI9C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,2EAA2E;IACpE,OAAO,SAAK;gBAEU,KAAK,EAAE,eAAe;IAE7C,UAAU,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAU7E;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IAEnD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;gBAE3B,IAAI,EAAE,eAAe;IAIjC,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAIzC;;;OAGG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAO7D"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObservationSink implementation that batches writes into HealthDataStore.
|
|
3
|
+
* Accumulates newRows across all writeBatch calls for the final IngestionResult.
|
|
4
|
+
* writeBatch is async so the adapter can await it and apply backpressure.
|
|
5
|
+
*/
|
|
6
|
+
export class StoreObservationSink {
|
|
7
|
+
store;
|
|
8
|
+
/** Accumulates the total count of NEW rows inserted across all batches. */
|
|
9
|
+
newRows = 0;
|
|
10
|
+
constructor(store) {
|
|
11
|
+
this.store = store;
|
|
12
|
+
}
|
|
13
|
+
async writeBatch(obs, labs) {
|
|
14
|
+
// upsertObservations is synchronous (better-sqlite3); returns NEW-row count.
|
|
15
|
+
if (obs.length > 0) {
|
|
16
|
+
this.newRows += this.store.upsertObservations(obs);
|
|
17
|
+
}
|
|
18
|
+
for (const lab of labs) {
|
|
19
|
+
this.newRows += this.store.upsertLabResult(lab);
|
|
20
|
+
}
|
|
21
|
+
// async signature lets the adapter await this call and pause/resume around it.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Registry of IngestionAdapters.
|
|
26
|
+
* register() adds adapters; importFile() dispatches to the first matching one.
|
|
27
|
+
* Throws a clear Error naming the file if no adapter canHandle it (sc-5-7).
|
|
28
|
+
*/
|
|
29
|
+
export class IngestionNormalizer {
|
|
30
|
+
adapters = [];
|
|
31
|
+
// bober: linear scan; swap for Map<ext, adapter> when adapter count grows large
|
|
32
|
+
sink;
|
|
33
|
+
constructor(sink) {
|
|
34
|
+
this.sink = sink;
|
|
35
|
+
}
|
|
36
|
+
/** Add an adapter to the registry. */
|
|
37
|
+
register(adapter) {
|
|
38
|
+
this.adapters.push(adapter);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Import a file using the first matching adapter.
|
|
42
|
+
* Throws if no adapter canHandle the file (message contains the file path).
|
|
43
|
+
*/
|
|
44
|
+
async importFile(filePath) {
|
|
45
|
+
const adapter = this.adapters.find((a) => a.canHandle(filePath));
|
|
46
|
+
if (!adapter) {
|
|
47
|
+
throw new Error(`No ingestion adapter can handle '${filePath}'`);
|
|
48
|
+
}
|
|
49
|
+
return adapter.ingest(filePath, this.sink);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=ingestion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingestion.js","sourceRoot":"","sources":["../../src/medical/ingestion.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,MAAM,OAAO,oBAAoB;IAIF;IAH7B,2EAA2E;IACpE,OAAO,GAAG,CAAC,CAAC;IAEnB,YAA6B,KAAsB;QAAtB,UAAK,GAAL,KAAK,CAAiB;IAAG,CAAC;IAEvD,KAAK,CAAC,UAAU,CAAC,GAAwB,EAAE,IAAiB;QAC1D,6EAA6E;QAC7E,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC;QACD,+EAA+E;IACjF,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,mBAAmB;IACb,QAAQ,GAAuB,EAAE,CAAC;IACnD,gFAAgF;IAC/D,IAAI,CAAkB;IAEvC,YAAY,IAAqB;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,sCAAsC;IACtC,QAAQ,CAAC,OAAyB;QAChC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,QAAgB;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,oCAAoC,QAAQ,GAAG,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;CACF"}
|