pressit 1.0.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.
- pressit/__init__.py +40 -0
- pressit/artifact.py +248 -0
- pressit/backends.py +113 -0
- pressit/engine.py +142 -0
- pressit/evidence.py +89 -0
- pressit/overlay.py +135 -0
- pressit/protocol.py +252 -0
- pressit-1.0.0.dist-info/METADATA +128 -0
- pressit-1.0.0.dist-info/RECORD +12 -0
- pressit-1.0.0.dist-info/WHEEL +5 -0
- pressit-1.0.0.dist-info/licenses/LICENSE +21 -0
- pressit-1.0.0.dist-info/top_level.txt +1 -0
pressit/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""pressit — front-facing conversion of the IFS interrogation engine.
|
|
2
|
+
|
|
3
|
+
IFS = Iterative Field Synthesis: structured adversarial interrogation of
|
|
4
|
+
contradictory or uncertain fields. See README.md for the plain-language
|
|
5
|
+
version. See CHANGELOG.md for what was decoupled from the original skill.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from . import artifact, backends, engine, evidence, overlay, protocol
|
|
9
|
+
from .artifact import (
|
|
10
|
+
StateArtifact, Take, Collision, Synthesis, PriorKeyEvaluation,
|
|
11
|
+
slugify_field, render, render_frontmatter, check_sections, load_prior,
|
|
12
|
+
)
|
|
13
|
+
from .backends import StubBackend, OpenRouterBackend, OllamaBackend
|
|
14
|
+
from .engine import LLMBackend, RunResult, build_user_message, parse_model_json, normalize, run
|
|
15
|
+
from .evidence import EvidenceItem, TAXONOMY, normalize_tag, EvidenceError
|
|
16
|
+
from .overlay import (
|
|
17
|
+
ProbeBrief, JargonFlag, ground, probe_jargon, handoff_seeds,
|
|
18
|
+
H_OBFUSCATION, H_GENUINE, H_BOTH, H_INSUFFICIENT,
|
|
19
|
+
)
|
|
20
|
+
from .protocol import (
|
|
21
|
+
SYSTEM_PROMPT, FieldInput, KeyDraft, ProtocolError,
|
|
22
|
+
required_collision_pairs, validate_keys,
|
|
23
|
+
STANDARD, SYSTEMIC, STRESS_TEST, ADJUDICATION,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__version__ = "1.0.0"
|
|
27
|
+
__all__ = [
|
|
28
|
+
"artifact", "backends", "engine", "evidence", "overlay", "protocol",
|
|
29
|
+
"StateArtifact", "Take", "Collision", "Synthesis", "PriorKeyEvaluation",
|
|
30
|
+
"slugify_field", "render", "render_frontmatter", "check_sections", "load_prior",
|
|
31
|
+
"StubBackend", "OpenRouterBackend", "OllamaBackend",
|
|
32
|
+
"LLMBackend", "RunResult", "build_user_message", "parse_model_json",
|
|
33
|
+
"normalize", "run",
|
|
34
|
+
"EvidenceItem", "TAXONOMY", "normalize_tag", "EvidenceError",
|
|
35
|
+
"ProbeBrief", "JargonFlag", "ground", "probe_jargon", "handoff_seeds",
|
|
36
|
+
"H_OBFUSCATION", "H_GENUINE", "H_BOTH", "H_INSUFFICIENT",
|
|
37
|
+
"SYSTEM_PROMPT", "FieldInput", "KeyDraft", "ProtocolError",
|
|
38
|
+
"required_collision_pairs", "validate_keys",
|
|
39
|
+
"STANDARD", "SYSTEMIC", "STRESS_TEST", "ADJUDICATION",
|
|
40
|
+
]
|
pressit/artifact.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""The state artifact: the portable, self-contained deliverable of an
|
|
2
|
+
interrogation.
|
|
3
|
+
|
|
4
|
+
One field, one canonical current state file, named
|
|
5
|
+
``interrogation-state-[field-slug].md``. Another person (or model) must be
|
|
6
|
+
able to continue the investigation from the artifact alone — never
|
|
7
|
+
"see above", never "as discussed", never unstated context, never memory.
|
|
8
|
+
|
|
9
|
+
Persistence boundary: the engine's responsibility ends when the complete
|
|
10
|
+
artifact has been emitted. The *caller* saves it to disk and supplies it to
|
|
11
|
+
future interrogations. The engine never writes files and never claims
|
|
12
|
+
external persistence occurred.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
|
|
20
|
+
from .protocol import (
|
|
21
|
+
KEY_CLASSIFICATIONS, CONFIDENCES, PRIOR_DISPOSITIONS,
|
|
22
|
+
SESSION_LIFECYCLES, PROTOCOL_NAME, PROTOCOL_VERSION,
|
|
23
|
+
required_collision_pairs, validate_keys, KeyDraft,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
SCHEMA_SECTIONS = (
|
|
27
|
+
"Interrogation Metadata", "Prior State", "1. Identify", "2. Decompose",
|
|
28
|
+
"3. Diverge", "4. Collide", "5. Refine", "6. Surprise",
|
|
29
|
+
"7. Synthesize", "State Status",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ArtifactError(ValueError):
|
|
34
|
+
"""Raised when an artifact breaks the canonical schema."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class PriorKeyEvaluation:
|
|
39
|
+
statement: str
|
|
40
|
+
status: str # HELD | CRACKED | MODIFIED | SUPERSEDED | UNRESOLVED
|
|
41
|
+
note: str
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
status = self.status.strip().upper()
|
|
45
|
+
if status not in PRIOR_DISPOSITIONS:
|
|
46
|
+
raise ArtifactError(f"Bad prior-Key disposition: {self.status!r}.")
|
|
47
|
+
self.status = status
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class Take:
|
|
52
|
+
id: str
|
|
53
|
+
title: str
|
|
54
|
+
argument: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Collision:
|
|
59
|
+
pair: str
|
|
60
|
+
contradiction: str
|
|
61
|
+
premise_failure: str
|
|
62
|
+
discriminator: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Synthesis:
|
|
67
|
+
established_ground: str
|
|
68
|
+
surviving_model: str
|
|
69
|
+
remaining_uncertainties: str
|
|
70
|
+
primary_next_target: str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class StateArtifact:
|
|
75
|
+
"""A complete, validated interrogation state."""
|
|
76
|
+
|
|
77
|
+
field: str
|
|
78
|
+
session: int
|
|
79
|
+
date: str # YYYY-MM-DD
|
|
80
|
+
depth: str # one-pass | deep
|
|
81
|
+
trigger_context: str
|
|
82
|
+
lifecycle: str # INITIAL | ITERATIVE | FINAL
|
|
83
|
+
prior_interrogation: int | None
|
|
84
|
+
prior_date: str | None
|
|
85
|
+
prior_keys: list[PriorKeyEvaluation]
|
|
86
|
+
takes: list[Take]
|
|
87
|
+
collisions: list[Collision]
|
|
88
|
+
keys: list[KeyDraft]
|
|
89
|
+
surprise: str | None
|
|
90
|
+
synthesis: Synthesis
|
|
91
|
+
evidence_summary: str = ""
|
|
92
|
+
interrogation: int = 1
|
|
93
|
+
|
|
94
|
+
def __post_init__(self) -> None:
|
|
95
|
+
if self.session < 1:
|
|
96
|
+
raise ArtifactError("Session number must be >= 1.")
|
|
97
|
+
if self.lifecycle not in SESSION_LIFECYCLES:
|
|
98
|
+
raise ArtifactError(f"Bad lifecycle: {self.lifecycle!r}.")
|
|
99
|
+
pairs = required_collision_pairs([t.id for t in self.takes])
|
|
100
|
+
got = [c.pair for c in self.collisions]
|
|
101
|
+
if got != pairs:
|
|
102
|
+
raise ArtifactError(
|
|
103
|
+
f"Collision pairs must be exactly {pairs}, got {got}."
|
|
104
|
+
)
|
|
105
|
+
validate_keys(self.keys)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def slug(self) -> str:
|
|
109
|
+
return slugify_field(self.field)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def filename(self) -> str:
|
|
113
|
+
return f"interrogation-state-{self.slug}.md"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def slugify_field(name: str) -> str:
|
|
117
|
+
slug = name.lower()
|
|
118
|
+
slug = re.sub(r"['\"]", "", slug)
|
|
119
|
+
slug = re.sub(r"[^a-z0-9]+", "-", slug)
|
|
120
|
+
slug = slug.strip("-")[:64].strip("-")
|
|
121
|
+
return slug or "field"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def render_frontmatter(a: StateArtifact) -> str:
|
|
125
|
+
return (
|
|
126
|
+
"---\n"
|
|
127
|
+
"artifact: interrogation-state\n"
|
|
128
|
+
f"field: {a.slug}\n"
|
|
129
|
+
f"interrogation: {a.interrogation}\n"
|
|
130
|
+
f"date: {a.date}\n"
|
|
131
|
+
f"depth: {a.depth}\n"
|
|
132
|
+
f"trigger_context: {a.trigger_context}\n"
|
|
133
|
+
"status: complete\n"
|
|
134
|
+
f"lifecycle: {a.lifecycle}\n"
|
|
135
|
+
f"protocol: {PROTOCOL_NAME}/{PROTOCOL_VERSION}\n"
|
|
136
|
+
"persistence: external\n"
|
|
137
|
+
"canonical: true\n"
|
|
138
|
+
"---"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def render(a: StateArtifact) -> str:
|
|
143
|
+
"""Render the full canonical Markdown artifact."""
|
|
144
|
+
a.__post_init__() # re-validate before emission
|
|
145
|
+
fm = render_frontmatter(a)
|
|
146
|
+
|
|
147
|
+
prior_lines = [
|
|
148
|
+
f"- Prior interrogation: {a.prior_interrogation if a.prior_interrogation else 'Not supplied'}",
|
|
149
|
+
f"- Prior date: {a.prior_date or '—'}",
|
|
150
|
+
]
|
|
151
|
+
if a.prior_keys:
|
|
152
|
+
prior_lines.append("- Key dispositions:")
|
|
153
|
+
for k in a.prior_keys:
|
|
154
|
+
prior_lines.append(f" - {k.status}: {k.statement} — {k.note}")
|
|
155
|
+
else:
|
|
156
|
+
prior_lines.append("- Key dispositions: none (session 1)")
|
|
157
|
+
|
|
158
|
+
take_lines = []
|
|
159
|
+
for t in a.takes:
|
|
160
|
+
take_lines.append(f"### Take {t.id} — {t.title}\n\n{t.argument}")
|
|
161
|
+
|
|
162
|
+
collision_lines = []
|
|
163
|
+
for c in a.collisions:
|
|
164
|
+
collision_lines.append(
|
|
165
|
+
f"### Pair {c.pair}\n\n"
|
|
166
|
+
f"- Contradiction: {c.contradiction}\n"
|
|
167
|
+
f"- Premise that must break: {c.premise_failure}\n"
|
|
168
|
+
f"- Discriminator: {c.discriminator}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
key_lines = []
|
|
172
|
+
for i, k in enumerate(a.keys, 1):
|
|
173
|
+
key_lines.append(
|
|
174
|
+
f"### Key {i}\n\n"
|
|
175
|
+
f"- Statement: {k.statement}\n"
|
|
176
|
+
f"- Classification: {k.classification}\n"
|
|
177
|
+
f"- Evidence: {k.evidence}\n"
|
|
178
|
+
f"- Confidence: {k.confidence}\n"
|
|
179
|
+
f"- Structural vulnerability: {k.vulnerability}\n"
|
|
180
|
+
f"- Falsifier: {k.falsifier}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
body = "\n\n".join([
|
|
184
|
+
fm,
|
|
185
|
+
f"# Interrogation State: {a.field}",
|
|
186
|
+
"## Interrogation Metadata\n"
|
|
187
|
+
f"- Date: {a.date}\n"
|
|
188
|
+
f"- Field: {a.field}\n"
|
|
189
|
+
f"- Depth: {a.depth}\n"
|
|
190
|
+
f"- Interrogation: {a.interrogation}\n"
|
|
191
|
+
f"- Trigger context: {a.trigger_context}\n"
|
|
192
|
+
f"- Protocol: {PROTOCOL_NAME}/{PROTOCOL_VERSION}\n"
|
|
193
|
+
f"- Lifecycle: {a.lifecycle}",
|
|
194
|
+
"## Prior State\n" + "\n".join(prior_lines),
|
|
195
|
+
f"## 1. Identify\n- Field: {a.field}\n- Trigger: {a.trigger_context}",
|
|
196
|
+
f"## 2. Decompose\n{a.evidence_summary or '(evidence logged with the interrogation)'}",
|
|
197
|
+
"## 3. Diverge\n\n" + "\n\n".join(take_lines),
|
|
198
|
+
"## 4. Collide\n\n" + "\n\n".join(collision_lines),
|
|
199
|
+
"## 5. Refine\n\n" + "\n\n".join(key_lines),
|
|
200
|
+
"## 6. Surprise\n\n" + (a.surprise or "No material Surprise identified."),
|
|
201
|
+
"## 7. Synthesize\n"
|
|
202
|
+
f"- Established: {a.synthesis.established_ground}\n"
|
|
203
|
+
f"- Surviving model: {a.synthesis.surviving_model}\n"
|
|
204
|
+
f"- Remaining uncertainties: {a.synthesis.remaining_uncertainties}\n"
|
|
205
|
+
f"- Primary next target: {a.synthesis.primary_next_target}",
|
|
206
|
+
"## State Status\n"
|
|
207
|
+
f"- Interrogation: Complete\n"
|
|
208
|
+
f"- Lifecycle: {a.lifecycle}\n"
|
|
209
|
+
f"- State: Canonical\n"
|
|
210
|
+
f"- Persistence: External\n"
|
|
211
|
+
"- External save: performed by the caller, not the engine",
|
|
212
|
+
])
|
|
213
|
+
return body + "\n"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def check_sections(markdown: str) -> list[str]:
|
|
217
|
+
"""Return any canonical schema sections missing from rendered Markdown."""
|
|
218
|
+
return [s for s in SCHEMA_SECTIONS if f"## {s}" not in markdown]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass
|
|
222
|
+
class PriorState:
|
|
223
|
+
"""A loaded prior artifact for stress-testing in session 2+."""
|
|
224
|
+
|
|
225
|
+
artifact: StateArtifact
|
|
226
|
+
|
|
227
|
+
def dispositions(self) -> list[PriorKeyEvaluation]:
|
|
228
|
+
return self.artifact.prior_keys
|
|
229
|
+
|
|
230
|
+
def next_session(self) -> int:
|
|
231
|
+
return self.artifact.interrogation + 1
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def load_prior(markdown: str) -> dict:
|
|
235
|
+
"""Extract the prior Keys and session number from a saved artifact.
|
|
236
|
+
|
|
237
|
+
Returns {'interrogation': int, 'keys': [statements...]}. The engine uses
|
|
238
|
+
this to stress-test each prior Key and assign dispositions — it never
|
|
239
|
+
inherits a prior conclusion as established.
|
|
240
|
+
"""
|
|
241
|
+
keys: list[str] = []
|
|
242
|
+
for m in re.finditer(r"- Statement:\s*(.+)", markdown):
|
|
243
|
+
keys.append(m.group(1).strip())
|
|
244
|
+
session = 1
|
|
245
|
+
m = re.search(r"^interrogation:\s*(\d+)", markdown, re.M)
|
|
246
|
+
if m:
|
|
247
|
+
session = int(m.group(1))
|
|
248
|
+
return {"interrogation": session, "keys": keys}
|
pressit/backends.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Swappable LLM backends for the engine.
|
|
2
|
+
|
|
3
|
+
- StubBackend: deterministic canned JSON for tests and demos. No network.
|
|
4
|
+
- OpenRouterBackend: OpenRouter chat completions (documented endpoint
|
|
5
|
+
https://openrouter.ai/api/v1/chat/completions). Key comes from the
|
|
6
|
+
OPENROUTER_API_KEY environment variable at call time — never stored in
|
|
7
|
+
files, never logged. Free-tier friendly.
|
|
8
|
+
- OllamaBackend: a local Ollama server (default http://localhost:11434),
|
|
9
|
+
for fully offline runs on Ryan-style local setups.
|
|
10
|
+
|
|
11
|
+
Only the backend you choose is used. No credentials are exchanged in chat
|
|
12
|
+
or files.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import urllib.request
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StubBackend:
|
|
23
|
+
"""Deterministic backend for tests and offline demos."""
|
|
24
|
+
|
|
25
|
+
name = "stub"
|
|
26
|
+
|
|
27
|
+
def __init__(self, artifact_json: dict) -> None:
|
|
28
|
+
self._artifact_json = artifact_json
|
|
29
|
+
|
|
30
|
+
def complete(self, system: str, user: str) -> str:
|
|
31
|
+
return json.dumps(self._artifact_json)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class OpenRouterBackend:
|
|
35
|
+
"""OpenRouter chat completions backend.
|
|
36
|
+
|
|
37
|
+
Reads OPENROUTER_API_KEY from the environment at call time. Optional
|
|
38
|
+
OPENROUTER_MODEL selects the model (defaults to a free-tier model).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name = "openrouter"
|
|
42
|
+
ENDPOINT = "https://openrouter.ai/api/v1/chat/completions"
|
|
43
|
+
|
|
44
|
+
def __init__(self, model: str | None = None, endpoint: str | None = None) -> None:
|
|
45
|
+
self.model = model or os.environ.get("OPENROUTER_MODEL", "")
|
|
46
|
+
self.endpoint = endpoint or os.environ.get("OPENROUTER_ENDPOINT") or self.ENDPOINT
|
|
47
|
+
if not self.model:
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
"No model selected: pass model= or set OPENROUTER_MODEL."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def complete(self, system: str, user: str) -> str:
|
|
53
|
+
key = os.environ.get("OPENROUTER_API_KEY")
|
|
54
|
+
if not key:
|
|
55
|
+
raise RuntimeError(
|
|
56
|
+
"OPENROUTER_API_KEY is not set. Export it before running."
|
|
57
|
+
)
|
|
58
|
+
payload = json.dumps({
|
|
59
|
+
"model": self.model,
|
|
60
|
+
"messages": [
|
|
61
|
+
{"role": "system", "content": system},
|
|
62
|
+
{"role": "user", "content": user},
|
|
63
|
+
],
|
|
64
|
+
"temperature": 0.2,
|
|
65
|
+
"response_format": {"type": "json_object"},
|
|
66
|
+
}).encode()
|
|
67
|
+
req = urllib.request.Request(
|
|
68
|
+
self.endpoint, data=payload,
|
|
69
|
+
headers={
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"Authorization": f"Bearer {key}",
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
try:
|
|
75
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
76
|
+
body = json.loads(resp.read().decode())
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
raise RuntimeError(f"OpenRouter request failed: {exc}") from exc
|
|
79
|
+
try:
|
|
80
|
+
return body["choices"][0]["message"]["content"]
|
|
81
|
+
except (KeyError, IndexError) as exc:
|
|
82
|
+
raise RuntimeError(f"Unexpected OpenRouter response shape: {body!r}") from exc
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class OllamaBackend:
|
|
86
|
+
"""Local Ollama backend (http://localhost:11434 by default)."""
|
|
87
|
+
|
|
88
|
+
name = "ollama"
|
|
89
|
+
|
|
90
|
+
def __init__(self, model: str = "llama3.1",
|
|
91
|
+
base_url: str = "http://localhost:11434") -> None:
|
|
92
|
+
self.model = model
|
|
93
|
+
self.base_url = base_url.rstrip("/")
|
|
94
|
+
|
|
95
|
+
def complete(self, system: str, user: str) -> str:
|
|
96
|
+
payload = json.dumps({
|
|
97
|
+
"model": self.model,
|
|
98
|
+
"system": system,
|
|
99
|
+
"prompt": user,
|
|
100
|
+
"stream": False,
|
|
101
|
+
"format": "json",
|
|
102
|
+
"options": {"temperature": 0.2},
|
|
103
|
+
}).encode()
|
|
104
|
+
req = urllib.request.Request(
|
|
105
|
+
f"{self.base_url}/api/generate", data=payload,
|
|
106
|
+
headers={"Content-Type": "application/json"},
|
|
107
|
+
)
|
|
108
|
+
try:
|
|
109
|
+
with urllib.request.urlopen(req, timeout=300) as resp:
|
|
110
|
+
body = json.loads(resp.read().decode())
|
|
111
|
+
except Exception as exc:
|
|
112
|
+
raise RuntimeError(f"Ollama request failed: {exc}") from exc
|
|
113
|
+
return body.get("response", "")
|
pressit/engine.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Engine: LLM backend interface and the interrogation runner.
|
|
2
|
+
|
|
3
|
+
The runner is provider-agnostic: any model that can follow the protocol
|
|
4
|
+
prompt works. Backends are swappable — see backends.py. The runner:
|
|
5
|
+
|
|
6
|
+
1. Builds the user message from the field, overlay flag, and prior artifact.
|
|
7
|
+
2. Calls the backend.
|
|
8
|
+
3. Parses the model's JSON (fences tolerated).
|
|
9
|
+
4. Normalizes: takes filtered to A/B/C, collisions restricted to required
|
|
10
|
+
pairs, keys capped at 5, probe dropped when the overlay is off.
|
|
11
|
+
5. Returns a normalized artifact dict the caller can render with
|
|
12
|
+
artifact.render().
|
|
13
|
+
|
|
14
|
+
No network happens here; the backend decides how the call is made.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Protocol, runtime_checkable
|
|
22
|
+
|
|
23
|
+
from .protocol import SYSTEM_PROMPT, FieldInput, required_collision_pairs
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EngineError(RuntimeError):
|
|
27
|
+
"""The model call failed or returned an unreadable artifact."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@runtime_checkable
|
|
31
|
+
class LLMBackend(Protocol):
|
|
32
|
+
"""Anything that can take (system, user) and return raw model text."""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
|
|
36
|
+
def complete(self, system: str, user: str) -> str: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_user_message(field_text: str, *, overlay: bool,
|
|
40
|
+
prior_json: str | None) -> str:
|
|
41
|
+
parts = [f"OVERLAY: {'ON' if overlay else 'OFF'}"]
|
|
42
|
+
if prior_json:
|
|
43
|
+
parts.append(
|
|
44
|
+
"PRIOR ARTIFACT (test these Keys, do not inherit Takes as authority):\n"
|
|
45
|
+
+ prior_json
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
parts.append("PRIOR ARTIFACT: none")
|
|
49
|
+
parts.append(f"FIELD:\n{field_text}")
|
|
50
|
+
return "\n\n".join(parts)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse_model_json(raw: str) -> dict:
|
|
54
|
+
"""Extract the JSON object from model output; fences tolerated."""
|
|
55
|
+
text = raw.strip()
|
|
56
|
+
fenced = text.split("```")
|
|
57
|
+
body = fenced[1] if len(fenced) >= 3 else text
|
|
58
|
+
start = body.find("{")
|
|
59
|
+
end = body.rfind("}")
|
|
60
|
+
if start < 0 or end <= start:
|
|
61
|
+
raise EngineError(
|
|
62
|
+
"The engine did not return a state artifact. "
|
|
63
|
+
"Try a tighter field or a larger model."
|
|
64
|
+
)
|
|
65
|
+
try:
|
|
66
|
+
return json.loads(body[start: end + 1])
|
|
67
|
+
except json.JSONDecodeError as exc:
|
|
68
|
+
raise EngineError(f"The engine returned unreadable JSON: {exc}") from exc
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def normalize(field_text: str, *, overlay: bool, prior_session: int | None,
|
|
72
|
+
model: dict) -> dict:
|
|
73
|
+
"""Normalize raw model output into a validated artifact dict.
|
|
74
|
+
|
|
75
|
+
Enforces the hard limits in code: valid Take ids, required collision
|
|
76
|
+
pairs only, at most 5 Keys, probe dropped unless the overlay is on.
|
|
77
|
+
"""
|
|
78
|
+
takes = [t for t in model.get("takes", []) if t.get("id") in ("A", "B", "C")]
|
|
79
|
+
if len(takes) < 2:
|
|
80
|
+
raise EngineError(
|
|
81
|
+
f"The engine returned {len(takes)} valid Takes; the protocol requires 2-3."
|
|
82
|
+
)
|
|
83
|
+
pairs = required_collision_pairs([t["id"] for t in takes])
|
|
84
|
+
collisions = [c for c in model.get("collisions", []) if c.get("pair") in pairs]
|
|
85
|
+
keys = model.get("keys", [])
|
|
86
|
+
if len(keys) > 5:
|
|
87
|
+
keys = keys[:5]
|
|
88
|
+
if len(keys) < 3:
|
|
89
|
+
raise EngineError(
|
|
90
|
+
f"The engine returned only {len(keys)} Keys; the protocol requires 3-5."
|
|
91
|
+
)
|
|
92
|
+
meta = model.get("meta", {})
|
|
93
|
+
probe = model.get("probe") if overlay else None
|
|
94
|
+
|
|
95
|
+
session = (prior_session + 1) if prior_session is not None else 1
|
|
96
|
+
return {
|
|
97
|
+
"meta": {
|
|
98
|
+
"field": meta.get("field") or field_text[:80],
|
|
99
|
+
"status": meta.get("status") or ("ITERATIVE" if prior_session else "INITIAL"),
|
|
100
|
+
"session": session,
|
|
101
|
+
},
|
|
102
|
+
"field": model.get("field", {"objective": "", "scope": ""}),
|
|
103
|
+
"priorState": model.get("priorState", {"reference": None, "evaluations": []}),
|
|
104
|
+
"evidence": model.get("evidence", {"facts": [], "claims": [], "unknowns": []}),
|
|
105
|
+
"probe": probe,
|
|
106
|
+
"takes": takes,
|
|
107
|
+
"collisions": collisions,
|
|
108
|
+
"keys": keys,
|
|
109
|
+
"surprise": model.get("surprise"),
|
|
110
|
+
"synthesis": model.get("synthesis", {}),
|
|
111
|
+
"overlay": overlay,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class RunResult:
|
|
117
|
+
ok: bool
|
|
118
|
+
artifact: dict | None = None
|
|
119
|
+
error: str | None = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def run(field_input: FieldInput, backend: LLMBackend,
|
|
123
|
+
*, prior_json: str | None = None,
|
|
124
|
+
prior_session: int | None = None) -> RunResult:
|
|
125
|
+
"""Run one interrogation against a backend. One pass, never loops."""
|
|
126
|
+
FieldInput(
|
|
127
|
+
field=field_input.field, mode=field_input.mode,
|
|
128
|
+
overlay=field_input.overlay, evidence=field_input.evidence,
|
|
129
|
+
) # re-validate
|
|
130
|
+
user = build_user_message(field_input.field, overlay=field_input.overlay,
|
|
131
|
+
prior_json=prior_json)
|
|
132
|
+
try:
|
|
133
|
+
raw = backend.complete(SYSTEM_PROMPT, user)
|
|
134
|
+
except Exception as exc: # backend errors surface as engine errors
|
|
135
|
+
return RunResult(ok=False, error=f"Backend {backend.name} failed: {exc}")
|
|
136
|
+
try:
|
|
137
|
+
model = parse_model_json(raw)
|
|
138
|
+
artifact = normalize(field_input.field, overlay=field_input.overlay,
|
|
139
|
+
prior_session=prior_session, model=model)
|
|
140
|
+
except EngineError as exc:
|
|
141
|
+
return RunResult(ok=False, error=str(exc))
|
|
142
|
+
return RunResult(ok=True, artifact=artifact)
|
pressit/evidence.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Evidence taxonomy.
|
|
2
|
+
|
|
3
|
+
Every input to an interrogation is tagged with exactly one of these tiers.
|
|
4
|
+
The core rule is negative: never silently upgrade an assertion into a fact
|
|
5
|
+
or an inference into a conclusion. Tags stay visible through every phase.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
# --- Tier definitions -------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
FACT = "FACT"
|
|
15
|
+
OBSERVATION = "OBSERVATION"
|
|
16
|
+
CLAIM = "CLAIM"
|
|
17
|
+
INFERENCE = "INFERENCE"
|
|
18
|
+
ASSUMPTION = "ASSUMPTION"
|
|
19
|
+
HYPOTHESIS = "HYPOTHESIS"
|
|
20
|
+
REQUIREMENT = "REQUIREMENT"
|
|
21
|
+
CONSTRAINT = "CONSTRAINT"
|
|
22
|
+
DEPENDENCY = "DEPENDENCY"
|
|
23
|
+
UNKNOWN = "UNKNOWN"
|
|
24
|
+
# Overlay-only: set when the optional evasion-probe overlay is ON.
|
|
25
|
+
OBFUSCATION = "OBFUSCATION"
|
|
26
|
+
|
|
27
|
+
TAXONOMY: dict[str, str] = {
|
|
28
|
+
FACT: "Directly established, unassailable ground truth.",
|
|
29
|
+
OBSERVATION: "Reported or logged, but not independently verified.",
|
|
30
|
+
CLAIM: "An explicit assertion made by an actor or source.",
|
|
31
|
+
INFERENCE: "A conclusion logically drawn from facts or verified observations.",
|
|
32
|
+
ASSUMPTION: "Presumed true without adequate supporting evidence.",
|
|
33
|
+
HYPOTHESIS: "A proposed explanatory model that still needs stress-testing.",
|
|
34
|
+
REQUIREMENT: "A condition that must be true for a model to hold.",
|
|
35
|
+
CONSTRAINT: "A boundary condition limiting viable outcomes.",
|
|
36
|
+
DEPENDENCY: "An element relying directly on the state of another.",
|
|
37
|
+
UNKNOWN: "A critical variable that cannot presently be determined.",
|
|
38
|
+
OBFUSCATION: (
|
|
39
|
+
"Evasive maneuver or jargon shield masking responsibility. "
|
|
40
|
+
"Tag ONLY when the evasion-probe overlay is active and the evasion "
|
|
41
|
+
"hypothesis is supported by discriminating evidence."
|
|
42
|
+
),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
CORE_TAGS = frozenset(
|
|
46
|
+
{FACT, OBSERVATION, CLAIM, INFERENCE, ASSUMPTION, HYPOTHESIS,
|
|
47
|
+
REQUIREMENT, CONSTRAINT, DEPENDENCY, UNKNOWN}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EvidenceError(ValueError):
|
|
52
|
+
"""Raised when an evidence item breaks the taxonomy rules."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class EvidenceItem:
|
|
57
|
+
"""One tagged input to the interrogation."""
|
|
58
|
+
|
|
59
|
+
text: str
|
|
60
|
+
tag: str
|
|
61
|
+
|
|
62
|
+
def __post_init__(self) -> None:
|
|
63
|
+
tag = self.tag.strip().upper()
|
|
64
|
+
if tag not in TAXONOMY:
|
|
65
|
+
raise EvidenceError(
|
|
66
|
+
f"Unknown evidence tag {self.tag!r}. "
|
|
67
|
+
f"Valid tags: {', '.join(sorted(TAXONOMY))}."
|
|
68
|
+
)
|
|
69
|
+
if not self.text.strip():
|
|
70
|
+
raise EvidenceError("Evidence item text must not be empty.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def normalize_tag(tag: str, *, overlay_on: bool = False) -> str:
|
|
74
|
+
"""Validate and normalize a tag. OBFUSCATION needs the overlay on."""
|
|
75
|
+
normalized = tag.strip().upper()
|
|
76
|
+
if normalized not in TAXONOMY:
|
|
77
|
+
raise EvidenceError(f"Unknown evidence tag {tag!r}.")
|
|
78
|
+
if normalized == OBFUSCATION and not overlay_on:
|
|
79
|
+
raise EvidenceError(
|
|
80
|
+
"OBFUSCATION is overlay-only: it may be tagged only when the "
|
|
81
|
+
"evasion-probe overlay is active. Use CLAIM with a hypothesis "
|
|
82
|
+
"marker for suspected-but-untested evasion."
|
|
83
|
+
)
|
|
84
|
+
return normalized
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def tag_list() -> list[str]:
|
|
88
|
+
"""All valid tags, in canonical order."""
|
|
89
|
+
return list(TAXONOMY)
|
pressit/overlay.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""The evasion-probe overlay (opt-in).
|
|
2
|
+
|
|
3
|
+
Decoupling note: this is the mechanism of the skill-suite's "Field Forge"
|
|
4
|
+
overlay, renamed in plain language. The workshop stage names
|
|
5
|
+
(Anvil/Hammer/Furnace) are replaced by what each stage actually does:
|
|
6
|
+
|
|
7
|
+
- GROUND (was "Anvil"): map the exact situational state without forcing a
|
|
8
|
+
narrative resolution.
|
|
9
|
+
- PROBE (was "Hammer"): test — never presume — evasion hypotheses, including
|
|
10
|
+
jargon armor of any kind (corporate, legal, therapeutic, wellness).
|
|
11
|
+
- HANDOFF (was "Furnace"): build 2-3 Take seeds and feed them into the
|
|
12
|
+
kernel's Diverge phase, then stand down.
|
|
13
|
+
|
|
14
|
+
The overlay never replaces Diverge/Collide. Default: OFF. Activate only when
|
|
15
|
+
asked, or when the field is clearly an evasion / responsibility problem.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
|
|
22
|
+
GROUND = "ground"
|
|
23
|
+
PROBE = "probe"
|
|
24
|
+
HANDOFF = "handoff"
|
|
25
|
+
|
|
26
|
+
# Evasion hypotheses for jargon-armor testing. Every flagged instance of
|
|
27
|
+
# shield-vocabulary is run through H-obfuscation / H-genuine / H-both —
|
|
28
|
+
# never pre-judged as armor by category membership alone.
|
|
29
|
+
H_OBFUSCATION = "H-obfuscation"
|
|
30
|
+
H_GENUINE = "H-genuine"
|
|
31
|
+
H_BOTH = "H-both"
|
|
32
|
+
H_INSUFFICIENT = "insufficient-evidence"
|
|
33
|
+
|
|
34
|
+
JARGON_HYPOTHESES = (H_OBFUSCATION, H_GENUINE, H_BOTH, H_INSUFFICIENT)
|
|
35
|
+
|
|
36
|
+
SUPPORT_STATUSES = ("supported", "contested", "unsupported")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OverlayError(ValueError):
|
|
40
|
+
"""Raised when overlay input breaks the contract."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class JargonFlag:
|
|
45
|
+
"""One instance of possibly-evasive vocabulary."""
|
|
46
|
+
|
|
47
|
+
phrase: str
|
|
48
|
+
hypothesis: str = H_INSUFFICIENT
|
|
49
|
+
discriminating_evidence: str = ""
|
|
50
|
+
|
|
51
|
+
def __post_init__(self) -> None:
|
|
52
|
+
if self.hypothesis not in JARGON_HYPOTHESES:
|
|
53
|
+
raise OverlayError(f"Bad jargon hypothesis: {self.hypothesis!r}.")
|
|
54
|
+
if not self.phrase.strip():
|
|
55
|
+
raise OverlayError("Jargon flag needs the flagged phrase.")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class ProbeBrief:
|
|
60
|
+
"""The overlay's output: audited ground, tested hypotheses, Take seeds.
|
|
61
|
+
|
|
62
|
+
This is a *brief*, not an interrogation result — it is fed into the
|
|
63
|
+
kernel's Diverge phase. The kernel's state artifact stays canonical.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
field: str
|
|
67
|
+
held_tensions: list[str] = field(default_factory=list)
|
|
68
|
+
person_state_sequence: list[str] = field(default_factory=list)
|
|
69
|
+
corrections: list[str] = field(default_factory=list)
|
|
70
|
+
obfuscated_object: str = ""
|
|
71
|
+
obfuscation_support: str = H_INSUFFICIENT
|
|
72
|
+
apparent_function: str = ""
|
|
73
|
+
active_tactics: list[str] = field(default_factory=list)
|
|
74
|
+
jargon_flags: list[JargonFlag] = field(default_factory=list)
|
|
75
|
+
take_seeds: list[str] = field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
def __post_init__(self) -> None:
|
|
78
|
+
if not self.field.strip():
|
|
79
|
+
raise OverlayError("Probe brief needs a field name.")
|
|
80
|
+
if self.obfuscation_support not in SUPPORT_STATUSES + (H_INSUFFICIENT,):
|
|
81
|
+
raise OverlayError(f"Bad support status: {self.obfuscation_support!r}.")
|
|
82
|
+
|
|
83
|
+
def supported_obfuscation(self) -> bool:
|
|
84
|
+
"""True only when the evasion hypothesis has discriminating evidence.
|
|
85
|
+
|
|
86
|
+
The OBFUSCATION evidence tag may be used only in this case; anything
|
|
87
|
+
else stays CLAIM with a hypothesis marker.
|
|
88
|
+
"""
|
|
89
|
+
return self.obfuscation_support == "supported" and bool(
|
|
90
|
+
self.obfuscated_object.strip()
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def ground(held_tensions: list[str], person_states: list[str]) -> dict:
|
|
95
|
+
"""GROUND stage: hold contradictions live, time-index person states.
|
|
96
|
+
|
|
97
|
+
Contradictions are stated as parallel non-subordinating sentences:
|
|
98
|
+
"X is true. Y is also true." Never "X, but Y".
|
|
99
|
+
"""
|
|
100
|
+
for t in held_tensions:
|
|
101
|
+
if not t.strip():
|
|
102
|
+
raise OverlayError("Held tension must not be empty.")
|
|
103
|
+
return {"held_tensions": held_tensions, "person_state_sequence": person_states}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def probe_jargon(phrase: str, evidence_for_obfuscation: str,
|
|
107
|
+
evidence_for_genuine: str) -> JargonFlag:
|
|
108
|
+
"""Run one jargon instance through the hypothesis protocol.
|
|
109
|
+
|
|
110
|
+
Both hypotheses are tested against discriminating evidence. When the
|
|
111
|
+
evidence cannot distinguish them, the honest finding is
|
|
112
|
+
insufficient-evidence — never a verdict.
|
|
113
|
+
"""
|
|
114
|
+
has_obf = bool(evidence_for_obfuscation.strip())
|
|
115
|
+
has_gen = bool(evidence_for_genuine.strip())
|
|
116
|
+
if has_obf and not has_gen:
|
|
117
|
+
return JargonFlag(phrase, H_OBFUSCATION, evidence_for_obfuscation)
|
|
118
|
+
if has_gen and not has_obf:
|
|
119
|
+
return JargonFlag(phrase, H_GENUINE, evidence_for_genuine)
|
|
120
|
+
if has_obf and has_gen:
|
|
121
|
+
return JargonFlag(
|
|
122
|
+
phrase, H_BOTH,
|
|
123
|
+
f"for obfuscation: {evidence_for_obfuscation}; "
|
|
124
|
+
f"for genuine: {evidence_for_genuine}",
|
|
125
|
+
)
|
|
126
|
+
return JargonFlag(phrase, H_INSUFFICIENT, "no discriminating evidence yet")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def handoff_seeds(brief: ProbeBrief) -> list[str]:
|
|
130
|
+
"""HANDOFF stage: 2-3 Take seeds for the kernel's Diverge phase."""
|
|
131
|
+
if not (2 <= len(brief.take_seeds) <= 3):
|
|
132
|
+
raise OverlayError(
|
|
133
|
+
f"Handoff must produce 2-3 Take seeds, got {len(brief.take_seeds)}."
|
|
134
|
+
)
|
|
135
|
+
return list(brief.take_seeds)
|
pressit/protocol.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""The IFS interrogation protocol: phases, modes, collision rules, and the
|
|
2
|
+
prompt text that drives an LLM backend.
|
|
3
|
+
|
|
4
|
+
IFS = Iterative Field Synthesis. A *field* is any question, claim, or
|
|
5
|
+
contradiction you want to pressure-test. The engine's core invariant:
|
|
6
|
+
**confidence and correctness are independent variables** — interrogate the
|
|
7
|
+
gap between them.
|
|
8
|
+
|
|
9
|
+
Not related to Internal Family Systems therapy; the acronym collision is
|
|
10
|
+
coincidental and noted once here.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
|
|
17
|
+
from .evidence import EvidenceItem, normalize_tag
|
|
18
|
+
|
|
19
|
+
# --- Constants ---------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
PROTOCOL_NAME = "IFS"
|
|
22
|
+
PROTOCOL_VERSION = "1.0"
|
|
23
|
+
PROTOCOL_LINEAGE = (
|
|
24
|
+
"Front-facing conversion of the ifs-interrogation skill v2.0 (Ryan's "
|
|
25
|
+
"skill-suite, MIT). Mechanism preserved; original naming decoupled."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
MIN_KEYS = 3
|
|
29
|
+
MAX_KEYS = 5
|
|
30
|
+
MIN_TAKES = 2
|
|
31
|
+
MAX_TAKES = 3
|
|
32
|
+
TAKE_IDS = ("A", "B", "C")
|
|
33
|
+
|
|
34
|
+
KEY_CLASSIFICATIONS = (
|
|
35
|
+
"ESTABLISHED",
|
|
36
|
+
"STRONGLY INFERRED",
|
|
37
|
+
"PLAUSIBLE",
|
|
38
|
+
"SPECULATIVE",
|
|
39
|
+
"UNRESOLVED",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
CONFIDENCES = ("HIGH", "MODERATE", "LOW")
|
|
43
|
+
|
|
44
|
+
PRIOR_DISPOSITIONS = ("HELD", "CRACKED", "MODIFIED", "SUPERSEDED", "UNRESOLVED")
|
|
45
|
+
|
|
46
|
+
SESSION_LIFECYCLES = ("INITIAL", "ITERATIVE", "FINAL")
|
|
47
|
+
|
|
48
|
+
# --- Modes --------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
STANDARD = "standard" # one-pass, five phases (default)
|
|
51
|
+
SYSTEMIC = "systemic" # deep nine-stage run + meta-cycle
|
|
52
|
+
STRESS_TEST = "stress-test" # session 2+: test prior Keys against new evidence
|
|
53
|
+
ADJUDICATION = "adjudication" # judge a claim against a framework's standards
|
|
54
|
+
|
|
55
|
+
MODES = {
|
|
56
|
+
STANDARD: "Bounded fields: decisions, claims, single contradictions. One pass.",
|
|
57
|
+
SYSTEMIC: "Structural or multi-actor fields, recurring patterns. Nine stages, may iterate.",
|
|
58
|
+
STRESS_TEST: "Prior state exists. Every prior Key is dispositioned; only new Keys are added.",
|
|
59
|
+
ADJUDICATION: "A claim is judged against an explicit standard — full deep run, no compression.",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class Phase:
|
|
65
|
+
id: str
|
|
66
|
+
name: str
|
|
67
|
+
brief: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
ONE_PASS_PHASES: tuple[Phase, ...] = (
|
|
71
|
+
Phase("identify", "Identify & Decompose",
|
|
72
|
+
"Bound the field. Name the objective and scope. Tag every input. Do not upgrade evidence."),
|
|
73
|
+
Phase("diverge", "Diverge",
|
|
74
|
+
"Two Takes by default. A third Take only if a genuinely independent model exists. "
|
|
75
|
+
"Each Take is internally coherent, uses tagged evidence, makes its strongest case — no strawmen, no premature consensus."),
|
|
76
|
+
Phase("collide", "Collide",
|
|
77
|
+
"Force Takes into conflict: required pairs only. Each collision names the contradiction, "
|
|
78
|
+
"the premise that must break, and the discriminator evidence. Collision must hurt brittle claims."),
|
|
79
|
+
Phase("refine", "Refine",
|
|
80
|
+
f"Skeptic pass, then extract {MIN_KEYS}-{MAX_KEYS} load-bearing Keys. Prefer fewer, stronger. "
|
|
81
|
+
"A Key that cannot survive stress-testing is discarded, not softened."),
|
|
82
|
+
Phase("capture", "Surprise + Synthesis + Capture",
|
|
83
|
+
"Surprise must emerge from collision, not from a single Take. "
|
|
84
|
+
"Then the surviving model, and the complete portable state artifact."),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
DEEP_STAGES: tuple[Phase, ...] = (
|
|
88
|
+
Phase("identify", "Identify", "Bound the field, objective, scope."),
|
|
89
|
+
Phase("decompose", "Decompose", "Tag every input; name actors, temporal structure, existing contradictions."),
|
|
90
|
+
Phase("question", "Question", "Identify questions capable of materially changing the model. "
|
|
91
|
+
"For each: what is unknown, why it matters, which interpretations it affects, "
|
|
92
|
+
"what evidence would resolve it, what happens if YES / if NO."),
|
|
93
|
+
Phase("test", "Test", "Attempt to break the strongest interpretations. "
|
|
94
|
+
"Actively search for evidence that would make the favored explanation wrong. "
|
|
95
|
+
"Do not protect a preferred conclusion."),
|
|
96
|
+
Phase("collide", "Collide", "Force Takes into conflict; name contradictions, broken premises, discriminators."),
|
|
97
|
+
Phase("refine", "Refine", "Extract 3-5 load-bearing Keys; discard what cannot survive."),
|
|
98
|
+
Phase("surprise", "Surprise", "What is now visible that wasn't before? Force it."),
|
|
99
|
+
Phase("synthesize", "Synthesize", "Established / inferred / rejected / unresolved. The strongest surviving explanation."),
|
|
100
|
+
Phase("capture", "Capture State", "Emit the complete self-contained state artifact."),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
META_CYCLE = (
|
|
104
|
+
"Gather the field", "Map relationships", "Locate tensions & harmonies",
|
|
105
|
+
"Extract recurring structures", "Generate explanatory Keys",
|
|
106
|
+
"Stress-test Keys", "Update the field", "Iterate or capture",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# --- Collision rules -----------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
def required_collision_pairs(take_ids: list[str]) -> list[str]:
|
|
112
|
+
"""Required collision pairs given the number of Takes.
|
|
113
|
+
|
|
114
|
+
Two Takes -> one pair (A/B). Three Takes -> all three pairs (A/B, A/C, B/C).
|
|
115
|
+
No extra collisions are ever added.
|
|
116
|
+
"""
|
|
117
|
+
ids = [t.strip().upper() for t in take_ids]
|
|
118
|
+
if not (MIN_TAKES <= len(ids) <= MAX_TAKES):
|
|
119
|
+
raise ValueError(f"Takes must be {MIN_TAKES}-{MAX_TAKES}, got {len(ids)}.")
|
|
120
|
+
if ids != list(TAKE_IDS[: len(ids)]):
|
|
121
|
+
raise ValueError(f"Take ids must be A..B or A..C in order, got {ids}.")
|
|
122
|
+
pairs = ["A/B"]
|
|
123
|
+
if len(ids) == MAX_TAKES:
|
|
124
|
+
pairs += ["A/C", "B/C"]
|
|
125
|
+
return pairs
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ProtocolError(ValueError):
|
|
129
|
+
"""Raised when interrogation input breaks protocol rules."""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class KeyDraft:
|
|
134
|
+
"""A refined Key before validation: statement, classification, evidence,
|
|
135
|
+
confidence, structural vulnerability, falsifier."""
|
|
136
|
+
|
|
137
|
+
statement: str
|
|
138
|
+
classification: str
|
|
139
|
+
evidence: str
|
|
140
|
+
confidence: str
|
|
141
|
+
vulnerability: str
|
|
142
|
+
falsifier: str
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def validate_keys(keys: list[KeyDraft]) -> list[KeyDraft]:
|
|
146
|
+
"""Enforce the Key contract: 3-5 Keys, every component present and valid."""
|
|
147
|
+
if not (MIN_KEYS <= len(keys) <= MAX_KEYS):
|
|
148
|
+
raise ProtocolError(
|
|
149
|
+
f"Interrogations must extract {MIN_KEYS}-{MAX_KEYS} Keys, got {len(keys)}. "
|
|
150
|
+
"Prefer fewer, stronger — do not pad to reach the minimum."
|
|
151
|
+
)
|
|
152
|
+
for key in keys:
|
|
153
|
+
classification = key.classification.strip().upper()
|
|
154
|
+
if classification not in KEY_CLASSIFICATIONS:
|
|
155
|
+
raise ProtocolError(f"Bad Key classification: {key.classification!r}.")
|
|
156
|
+
confidence = key.confidence.strip().upper()
|
|
157
|
+
if confidence not in CONFIDENCES:
|
|
158
|
+
raise ProtocolError(f"Bad Key confidence: {key.confidence!r}.")
|
|
159
|
+
for part, name in (
|
|
160
|
+
(key.statement, "statement"),
|
|
161
|
+
(key.evidence, "evidence"),
|
|
162
|
+
(key.vulnerability, "vulnerability"),
|
|
163
|
+
(key.falsifier, "falsifier"),
|
|
164
|
+
):
|
|
165
|
+
if not part.strip():
|
|
166
|
+
raise ProtocolError(f"Key is missing its {name}: {key.statement[:60]!r}.")
|
|
167
|
+
return keys
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class FieldInput:
|
|
172
|
+
"""Everything the engine needs for one interrogation."""
|
|
173
|
+
|
|
174
|
+
field: str
|
|
175
|
+
mode: str = STANDARD
|
|
176
|
+
overlay: bool = False
|
|
177
|
+
evidence: list[EvidenceItem] = field(default_factory=list)
|
|
178
|
+
prior_artifact_path: str | None = None
|
|
179
|
+
|
|
180
|
+
def __post_init__(self) -> None:
|
|
181
|
+
if not self.field.strip():
|
|
182
|
+
raise ProtocolError("Field must not be empty.")
|
|
183
|
+
if self.mode not in MODES:
|
|
184
|
+
raise ProtocolError(f"Unknown mode {self.mode!r}. Valid: {', '.join(MODES)}.")
|
|
185
|
+
for item in self.evidence:
|
|
186
|
+
normalize_tag(item.tag, overlay_on=self.overlay)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# --- System prompt (drives any LLM backend) -------------------------------------
|
|
190
|
+
|
|
191
|
+
SYSTEM_PROMPT = """You are IFS v1.0 (Iterative Field Synthesis), a provider-agnostic one-pass interrogation engine.
|
|
192
|
+
|
|
193
|
+
CORE PRINCIPLE
|
|
194
|
+
Confidence and correctness are independent variables. Interrogate the gap between them.
|
|
195
|
+
|
|
196
|
+
EVIDENCE DISCIPLINE (never silently upgrade)
|
|
197
|
+
FACT — directly established
|
|
198
|
+
OBSERVATION — reported/logged, unverified
|
|
199
|
+
CLAIM — actor/source assertion
|
|
200
|
+
INFERENCE — logical conclusion
|
|
201
|
+
ASSUMPTION — unsupported presumption
|
|
202
|
+
HYPOTHESIS — proposed explanation
|
|
203
|
+
REQUIREMENT — condition a model needs
|
|
204
|
+
CONSTRAINT — limiting boundary
|
|
205
|
+
DEPENDENCY — state-dependent element
|
|
206
|
+
UNKNOWN — presently undetermined critical variable
|
|
207
|
+
OBFUSCATION — only if the evasion-probe overlay is ON: evasive maneuver masking responsibility
|
|
208
|
+
|
|
209
|
+
Never fabricate actors, facts, motives, events, or certainty.
|
|
210
|
+
|
|
211
|
+
ENGINE (run internally, one pass, no looping)
|
|
212
|
+
1. IDENTIFY & DECOMPOSE — name the field, objective, scope. Tag every input.
|
|
213
|
+
2. DIVERGE — 2 Takes by default; Take C only if a genuinely independent third model exists. Each Take uses tagged evidence, is internally coherent, makes its strongest case, avoids strawmen and premature consensus.
|
|
214
|
+
3. COLLIDE — required pairs only (A/B; A/C and B/C iff C exists). Never add extra collisions. Each collision: contradiction, premise that must break, discriminator evidence.
|
|
215
|
+
4. REFINE — 3-5 load-bearing Keys. Prefer fewer, stronger. Each Key: statement, classification (ESTABLISHED | STRONGLY INFERRED | PLAUSIBLE | SPECULATIVE | UNRESOLVED), evidence with tags, confidence (HIGH | MODERATE | LOW), structural vulnerability, falsifier.
|
|
216
|
+
5. SURPRISE + SYNTHESIS + CAPTURE — Surprise must emerge from collision, not from a single Take. If none: surprise = null. Synthesis: established ground, surviving model, remaining uncertainties, primary next target.
|
|
217
|
+
|
|
218
|
+
PRIOR STATE
|
|
219
|
+
If a prior artifact is supplied: test every prior Key. Mark HELD | CRACKED | MODIFIED | SUPERSEDED | UNRESOLVED. Add only genuinely new Keys. Session number = prior + 1. Status ITERATIVE (FINAL only if the field is resolved enough that the next target is idle). Never silently rewrite a prior Key; preserve failures visibly.
|
|
220
|
+
|
|
221
|
+
LIGHT COMPRESSION
|
|
222
|
+
Cut restated evidence, redundant framing, connective prose. If a sentence can be removed without losing a Key, discriminator, or classification, remove it.
|
|
223
|
+
|
|
224
|
+
HOLD CONTRADICTIONS LIVE
|
|
225
|
+
State opposing facts in parallel non-subordinating sentences. Do not use "but" / "however" / "which means" to quietly privilege one side.
|
|
226
|
+
|
|
227
|
+
EVASION-PROBE OVERLAY (only when overlay=true)
|
|
228
|
+
Ground: time-index person-states; classify corrections as Detail-Correction vs Core-Claim.
|
|
229
|
+
Probe: name the obfuscated object (the exact responsibility that may be dodged), apparent function, active tactics, jargon-armor flags. Isolate motive: log observed action and quotes only — never supply unstated motives. Test jargon claims as hypotheses (H-obfuscation / H-genuine / H-both); never pre-judge a category of language as armor.
|
|
230
|
+
Handoff: produce 2-3 Take seeds from the ground and stripped material, labeled as seeds, and feed them into Diverge. Then stand down.
|
|
231
|
+
If overlay is false, omit the probe entirely. Do not hunt for interpersonal evasion in a systems field.
|
|
232
|
+
|
|
233
|
+
OUTPUT
|
|
234
|
+
Return ONLY a JSON object matching this shape (no markdown fences, no preamble):
|
|
235
|
+
{
|
|
236
|
+
"meta": { "field": string, "status": "INITIAL"|"ITERATIVE"|"FINAL" },
|
|
237
|
+
"field": { "objective": string, "scope": string },
|
|
238
|
+
"priorState": { "reference": string|null, "evaluations": [{ "statement": string, "status": "HELD"|"CRACKED"|"MODIFIED"|"SUPERSEDED"|"UNRESOLVED", "note": string }] },
|
|
239
|
+
"evidence": {
|
|
240
|
+
"facts": [{ "text": string, "tag": "FACT"|"OBSERVATION"|"CONSTRAINT"|"REQUIREMENT" }],
|
|
241
|
+
"claims": [{ "text": string, "tag": "CLAIM"|"OBSERVATION"|"INFERENCE"|"ASSUMPTION"|"HYPOTHESIS"|"OBFUSCATION" }],
|
|
242
|
+
"unknowns": [{ "text": string, "tag": "UNKNOWN"|"DEPENDENCY"|"ASSUMPTION" }]
|
|
243
|
+
},
|
|
244
|
+
"probe": { "obfuscatedObject": string, "apparentFunction": string, "activeTactics": string[], "jargonFlags": string[] } | null,
|
|
245
|
+
"takes": [{ "id": "A"|"B"|"C", "title": string, "argument": string }],
|
|
246
|
+
"collisions": [{ "pair": "A/B"|"A/C"|"B/C", "contradiction": string, "premiseFailure": string, "discriminator": string }],
|
|
247
|
+
"keys": [{ "statement": string, "classification": string, "evidence": string, "confidence": "HIGH"|"MODERATE"|"LOW", "vulnerability": string, "falsifier": string }],
|
|
248
|
+
"surprise": string | null,
|
|
249
|
+
"synthesis": { "establishedGround": string, "survivingModel": string, "remainingUncertainties": string, "primaryNextTarget": string }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Hard limits: 2-3 takes, required collisions only, 3-5 keys, no TODOs, no placeholders, no motive attribution without evidence. Unresolved questions go in remainingUncertainties and primaryNextTarget — they do not trigger another pass."""
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pressit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Structured adversarial interrogation of contradictory or uncertain fields (IFS: Iterative Field Synthesis)
|
|
5
|
+
Author: Ryan Wesley Hitchcock
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ohpersonperson/frontface
|
|
8
|
+
Keywords: interrogation,adversarial-reasoning,evidence,red-teaming,decision-analysis
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# IFS — Iterative Field Synthesis
|
|
18
|
+
|
|
19
|
+
A structured way to pressure-test a claim, decision, or contradiction so it comes out the other side either **load-bearing** or **dead** — with the reasoning preserved in a portable document.
|
|
20
|
+
|
|
21
|
+
No prerequisites. You don't need to know anything about the project's history, any framework, or any belief system. The mechanism is the whole product.
|
|
22
|
+
|
|
23
|
+
*(Lineage: this is the front-facing conversion of the `ifs-interrogation` skill from Ryan's skill-suite, MIT. What changed in the conversion is documented in CHANGELOG.md — mechanism kept, original naming removed.)*
|
|
24
|
+
|
|
25
|
+
## What it does
|
|
26
|
+
|
|
27
|
+
You hand the engine a **field** — a situation, claim, or contradiction with real stakes and real uncertainty. The engine runs it through a fixed interrogation:
|
|
28
|
+
|
|
29
|
+
1. **Identify & Decompose** — bound the field, name the objective and scope, tag every input with an evidence tier (FACT, CLAIM, UNKNOWN, …). Evidence is never silently upgraded: a claim stays a claim until it's proven.
|
|
30
|
+
2. **Diverge** — build the 2 or 3 strongest internally-coherent cases (Takes). No strawmen, no premature consensus.
|
|
31
|
+
3. **Collide** — force the Takes into conflict. Every collision names the contradiction, the premise that must break, and the discriminator evidence that decides it. If the collision doesn't hurt brittle claims, it wasn't a real collision.
|
|
32
|
+
4. **Refine** — a skeptic pass, then extract 3–5 **load-bearing Keys**: crisp assertions, each with a classification, the tagged evidence behind it, a confidence level, its structural vulnerability, and its falsifier — the specific evidence that would overturn it. A Key that can't survive is discarded, not softened.
|
|
33
|
+
5. **Surprise + Synthesis + Capture** — the non-obvious result that emerged *from the collision* (not from one Take), the surviving model, and the complete **state artifact**: a self-contained Markdown document another person or model can continue from without any of the original conversation.
|
|
34
|
+
|
|
35
|
+
Core principle: **confidence and correctness are independent variables.** The engine interrogates the gap between them.
|
|
36
|
+
|
|
37
|
+
## Why it works
|
|
38
|
+
|
|
39
|
+
Most reasoning failures aren't stupidity — they're structural. Claims quietly upgrade into facts. The first plausible story wins and alternatives never get built. Contradictions get smoothed over with "however." IFS attacks all three mechanically:
|
|
40
|
+
|
|
41
|
+
- **Evidence tagging** makes silent upgrades impossible — every input carries its tier through every phase.
|
|
42
|
+
- **Forced Takes** mean the strongest opposing case gets built with the same care as your own.
|
|
43
|
+
- **Named collisions with discriminators** force a decision rule instead of vibes.
|
|
44
|
+
- **Keys carry falsifiers**, so the output tells you exactly what would prove it wrong.
|
|
45
|
+
- **Prior Keys are stress-tested, never inherited.** In session 2+, every old Key gets a verdict — HELD, CRACKED, MODIFIED, SUPERSEDED, UNRESOLVED — and failures stay visible in the artifact instead of being quietly rewritten.
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
Zero dependencies beyond Python 3.10+ and a model to run the engine on.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd ifs
|
|
53
|
+
python3 -m unittest discover -s tests # 41 offline tests — prove the machinery first
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
To run a real interrogation, you need a backend — any LLM that can follow the protocol prompt:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Fully offline: local Ollama
|
|
60
|
+
python3 examples/run_local.py my-field.txt --ollama
|
|
61
|
+
|
|
62
|
+
# OpenRouter (free-tier models work fine)
|
|
63
|
+
OPENROUTER_API_KEY=... OPENROUTER_MODEL="nvidia/nemotron-3-ultra-550b-a55b:free" \
|
|
64
|
+
python3 examples/run_local.py my-field.txt
|
|
65
|
+
|
|
66
|
+
# Session 2+: feed the prior artifact back in; old Keys get stress-tested
|
|
67
|
+
python3 examples/run_local.py new-evidence.txt --prior interrogation-state-my-field.md --ollama
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`my-field.txt` is just prose: the situation, the conflicting accounts, the stakes. The engine returns `interrogation-state-<slug>.md` — the portable artifact.
|
|
71
|
+
|
|
72
|
+
Or use it as a library:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from pressit import FieldInput, EvidenceItem, run, OllamaBackend
|
|
76
|
+
|
|
77
|
+
field = FieldInput(
|
|
78
|
+
field="Q3 revenue is up 41%. The owner calls it a turnaround. Is it?",
|
|
79
|
+
evidence=[
|
|
80
|
+
EvidenceItem("Q3 revenue $48k; Q2 $34k (POS records)", "FACT"),
|
|
81
|
+
EvidenceItem("A July estate sale moved ~2,100 rare books", "FACT"),
|
|
82
|
+
EvidenceItem("Owner: 'new regulars are driving it'", "CLAIM"),
|
|
83
|
+
EvidenceItem("Q4 inventory pipeline", "UNKNOWN"),
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
result = run(field, OllamaBackend())
|
|
87
|
+
assert result.ok
|
|
88
|
+
print(result.artifact["surprise"])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## The four modes
|
|
92
|
+
|
|
93
|
+
| Mode | When |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `standard` (default) | Bounded fields: decisions, claims, single contradictions. One pass, never loops. |
|
|
96
|
+
| `systemic` | Structural/multi-actor fields, recurring patterns. Nine stages; may iterate across sessions. |
|
|
97
|
+
| `stress-test` | Prior state exists. Every prior Key is dispositioned; only genuinely new Keys are added. |
|
|
98
|
+
| `adjudication` | A claim is judged against an explicit standard (a contract, a policy, a spec). Full deep run, no compression. |
|
|
99
|
+
|
|
100
|
+
The optional **evasion-probe overlay** (off by default) handles interpersonal fields where language may be doing evasive work: it maps the observable ground, tests evasion hypotheses (never presumes them — jargon is tested as H-obfuscation / H-genuine / H-both), and feeds Take seeds into Diverge. It never replaces the engine.
|
|
101
|
+
|
|
102
|
+
## Inputs, outputs
|
|
103
|
+
|
|
104
|
+
- **Input:** a field description (prose), optional tagged evidence, optional prior artifact, optional overlay.
|
|
105
|
+
- **Output:** a normalized artifact dict, or a rendered `interrogation-state-<slug>.md` — complete, self-contained, no placeholders, no TODOs.
|
|
106
|
+
- **The engine never writes files and never claims external persistence.** Your code saves the artifact; the engine's job ends when it's emitted. That's the persistence boundary, and it's deliberate.
|
|
107
|
+
|
|
108
|
+
## Worked examples
|
|
109
|
+
|
|
110
|
+
Three complete runs a stranger can follow, in `examples/`:
|
|
111
|
+
|
|
112
|
+
1. `example-1-corner-books.md` — standard one-pass: is a bookstore's revenue jump a turnaround or a blip?
|
|
113
|
+
2. `example-2-corner-books-session-2.md` — stress-test mode: new evidence meets old Keys; watch a Key get CRACKED and preserved visibly.
|
|
114
|
+
3. `example-3-sla-adjudication.md` — adjudication mode: a vendor's SLA claim judged against the contract text.
|
|
115
|
+
|
|
116
|
+
*All three fields are constructed for teaching — they show the engine's shape, not real findings.*
|
|
117
|
+
|
|
118
|
+
## FAQ
|
|
119
|
+
|
|
120
|
+
**Is this related to Internal Family Systems therapy?** No. The acronym collision is coincidental; this is an interrogation engine, not a therapeutic method.
|
|
121
|
+
|
|
122
|
+
**Does it need an internet connection?** Only for the model call, and only the backend you choose. With Ollama it's fully offline. The 41 unit tests never touch the network.
|
|
123
|
+
|
|
124
|
+
**What does it cost?** The package is free and dependency-free. The model call costs whatever your provider charges — free-tier models work; the engine was evaluated on them.
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT. See LICENSE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pressit/__init__.py,sha256=mXmDgXCfJjs_RBYPWPASgyeALo8JeeySo67l-SpnpwI,1897
|
|
2
|
+
pressit/artifact.py,sha256=wS806AXRqi_ON2kjQRE-N4EwU3UUXsAGqIUITr64wiQ,7946
|
|
3
|
+
pressit/backends.py,sha256=V1KSIlbzh8ow7gMi4WvUM0-hionE4XfYKhP5Ydl6udk,3981
|
|
4
|
+
pressit/engine.py,sha256=8jtPdwFz84l26E3-n0tWyC7DP9MCDa_27yrp9tjSoCk,5205
|
|
5
|
+
pressit/evidence.py,sha256=4bfTgDLoa5ocoRHCvhimy5LHcM3hZc-hFRHmopsxsN8,3161
|
|
6
|
+
pressit/overlay.py,sha256=h4HaRpirLrFpbb8dhhxqJnKLZbtPsozmIP_YSJdRxWc,5115
|
|
7
|
+
pressit/protocol.py,sha256=qyJH5dYtXNXkTYZUA8BK5ywcFqHQUMc7_pivoUuqVSc,12474
|
|
8
|
+
pressit-1.0.0.dist-info/licenses/LICENSE,sha256=Zx_XafyZDq7qBexUb7tER9kYlBZ0rCVaAwKuqMCfxLE,1078
|
|
9
|
+
pressit-1.0.0.dist-info/METADATA,sha256=-AXDgH9HvcDKIEWNNqtXOK7icos-sVDmvjUDRsKyAv8,7496
|
|
10
|
+
pressit-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
11
|
+
pressit-1.0.0.dist-info/top_level.txt,sha256=zPmo5-EO3gsp9sYNXa4vSJweLWwapVhyKucspMl4PQ8,8
|
|
12
|
+
pressit-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ryan Wesley Hitchcock
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pressit
|