postmo 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- postmo-1.0.0/LICENSE +21 -0
- postmo-1.0.0/PKG-INFO +186 -0
- postmo-1.0.0/README.md +170 -0
- postmo-1.0.0/pyproject.toml +27 -0
- postmo-1.0.0/setup.cfg +4 -0
- postmo-1.0.0/src/postmo/__init__.py +49 -0
- postmo-1.0.0/src/postmo/disciplines.py +138 -0
- postmo-1.0.0/src/postmo/evidence.py +146 -0
- postmo-1.0.0/src/postmo/ledger.py +132 -0
- postmo-1.0.0/src/postmo/overlay.py +74 -0
- postmo-1.0.0/src/postmo/record.py +352 -0
- postmo-1.0.0/src/postmo/scans.py +433 -0
- postmo-1.0.0/src/postmo.egg-info/PKG-INFO +186 -0
- postmo-1.0.0/src/postmo.egg-info/SOURCES.txt +17 -0
- postmo-1.0.0/src/postmo.egg-info/dependency_links.txt +1 -0
- postmo-1.0.0/src/postmo.egg-info/top_level.txt +1 -0
- postmo-1.0.0/tests/test_ledger.py +54 -0
- postmo-1.0.0/tests/test_record.py +208 -0
- postmo-1.0.0/tests/test_scans.py +198 -0
postmo-1.0.0/LICENSE
ADDED
|
@@ -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.
|
postmo-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: postmo
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Hold a messy multi-person situation without resolving it: five audit disciplines as linter checks plus a full structured audit record.
|
|
5
|
+
Author: Ryan Wesley Hitchcock
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ohpersonperson/frontface
|
|
8
|
+
Keywords: audit,contradiction,linter,evidence,interrogation
|
|
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
|
+
# postmo — hold the mess, don't resolve it
|
|
18
|
+
|
|
19
|
+
An audit is not a narrative. A narrative resolves tension toward a
|
|
20
|
+
single coherent story; an audit holds the actual shape of what's been
|
|
21
|
+
said — including the parts that don't resolve — and tracks it
|
|
22
|
+
accurately as it changes.
|
|
23
|
+
|
|
24
|
+
Five disciplines, enforced where they're mechanical and explicit
|
|
25
|
+
protocol where they need judgment:
|
|
26
|
+
|
|
27
|
+
1. **Hold contradictions as contradictions.** "I adored her and I'm
|
|
28
|
+
furious at her" stays two live halves — never "X, but Y", never a
|
|
29
|
+
synthesized third thing, never flagged as a problem needing
|
|
30
|
+
resolution.
|
|
31
|
+
2. **Track people as time-indexed states, not verdicts.** Tuesday-her
|
|
32
|
+
and Thursday-her are both real. Nobody is "a liar" as a fixed label.
|
|
33
|
+
3. **Type every correction: detail vs core-claim.** A fixed date doesn't
|
|
34
|
+
reopen standing claims; a withdrawn accusation is the signal.
|
|
35
|
+
4. **Never supply motive the teller didn't state.** "He tackled you
|
|
36
|
+
because he panicked" is invented, even if plausible. Observed
|
|
37
|
+
sequence only — who did what, in what order.
|
|
38
|
+
5. **Self-check for softening.** Did "betrayal" become "mistake"? Did a
|
|
39
|
+
restatement tidy the account? Did ambivalence get resolved into one
|
|
40
|
+
"real" feeling? The noticing is part of the deliverable.
|
|
41
|
+
|
|
42
|
+
No prerequisites. No model, no network — the operator (human or model)
|
|
43
|
+
supplies the material; the library enforces the discipline. The
|
|
44
|
+
mechanism is the whole product.
|
|
45
|
+
|
|
46
|
+
*(Lineage: the front-facing conversion of the `forensic-situational-audit`
|
|
47
|
+
skill from Ryan's skill-suite, MIT. What changed is documented in
|
|
48
|
+
CHANGELOG.md — the name, the "field" language, and prose rules turned
|
|
49
|
+
into checked code.)*
|
|
50
|
+
|
|
51
|
+
## What it does
|
|
52
|
+
|
|
53
|
+
Two modes:
|
|
54
|
+
|
|
55
|
+
- **Direct audit (the headline).** "Don't resolve this, just hold it."
|
|
56
|
+
The operator works a messy multi-person situation conversationally;
|
|
57
|
+
the package scans drafts for discipline violations and accumulates
|
|
58
|
+
the audit record. No engine, no interrogation.
|
|
59
|
+
- **Overlay.** Attaches to the interrogation engine's Decompose phase
|
|
60
|
+
(disciplines 2–4) and Collide phase (disciplines 1, 5 — the strict
|
|
61
|
+
enforcer: no subordinating conjunctions, no synthesized third thing).
|
|
62
|
+
The overlay never resolves, never synthesizes, never converts a
|
|
63
|
+
direct audit into an interrogation unasked.
|
|
64
|
+
|
|
65
|
+
The canonical artifact is the **audit record**: which disciplines
|
|
66
|
+
fired, held tensions, person-state sequences, typed corrections,
|
|
67
|
+
softening and motive flags — structured data with timestamps that the
|
|
68
|
+
next session can load and continue from. Machine-readable first
|
|
69
|
+
(`record.render()`), renderable conversationally
|
|
70
|
+
(`record.render_conversation()`).
|
|
71
|
+
|
|
72
|
+
## Why it works
|
|
73
|
+
|
|
74
|
+
Most "holding space" collapses into narrative smoothing — the grammar
|
|
75
|
+
does the resolving before anyone notices. The audit attacks that
|
|
76
|
+
mechanically:
|
|
77
|
+
|
|
78
|
+
- **The subordinator scan** rejects "X, but/however/which means Y" in
|
|
79
|
+
held tensions. The "but" demotes one side; the audit refuses to hold
|
|
80
|
+
a tension that's already been resolved in grammar.
|
|
81
|
+
- **The motive scan** flags causal attributions with no teller
|
|
82
|
+
attribution ("because he panicked") while passing attributed ones
|
|
83
|
+
("he said he panicked") and keeping marked guesses marked.
|
|
84
|
+
- **The softening scan** compares the teller's words against
|
|
85
|
+
restatements: downgrade wordlist ("betrayal" → "mistake"), dropped
|
|
86
|
+
strong terms, tidy-restatement phrases ("so what you're saying is"),
|
|
87
|
+
ambivalence-resolution phrases ("the real feeling is").
|
|
88
|
+
- **The stand-down rule** is schema-level: the record rejects
|
|
89
|
+
synthesis/resolution/verdict sections. An audit that resolves has
|
|
90
|
+
stopped auditing.
|
|
91
|
+
|
|
92
|
+
What the scans can't do stays explicitly operator protocol — each
|
|
93
|
+
discipline's table entry (`disciplines.py`) marks the code/operator
|
|
94
|
+
split. A fired check is a finding; a quiet check is not a clean bill
|
|
95
|
+
of health.
|
|
96
|
+
|
|
97
|
+
## Quickstart
|
|
98
|
+
|
|
99
|
+
Zero dependencies beyond Python 3.10+. Fully offline.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cd audit
|
|
103
|
+
PYTHONPATH=src python3 -m unittest discover -s tests # 55 offline tests
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Lint an audit draft:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
PYTHONPATH=src python3 examples/lint_audit.py --text examples/bad-draft.txt \
|
|
110
|
+
--restatement examples/bad-restatement.txt
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Build a record in code:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from postmo import (
|
|
117
|
+
AuditRecord, HeldTension, PersonState, Correction, scan_text,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
rec = AuditRecord(case_name="Warehouse shift dispute")
|
|
121
|
+
rec.tensions.append(HeldTension(text=(
|
|
122
|
+
"Dana says the pallet was already damaged. "
|
|
123
|
+
"Marco says it was intact.")))
|
|
124
|
+
rec.states.append(PersonState(when="2026-09-18", person="Dana",
|
|
125
|
+
state="calm, cooperative"))
|
|
126
|
+
rec.corrections.append(Correction(kind="core-claim",
|
|
127
|
+
text="Marco withdrew the claim that Dana hid the damage."))
|
|
128
|
+
rec.log(1, "enforced", "1 tension held as parallel sentences")
|
|
129
|
+
|
|
130
|
+
report = scan_text("He tackled you because he panicked.")
|
|
131
|
+
for flag in report.motive_flags:
|
|
132
|
+
print(flag.severity, ":", flag.sentence)
|
|
133
|
+
|
|
134
|
+
print(rec.render()) # the machine-readable record
|
|
135
|
+
print(rec.render_conversation()) # the human surface
|
|
136
|
+
|
|
137
|
+
# Next session:
|
|
138
|
+
from postmo import parse
|
|
139
|
+
rec2 = parse(rec.render()) # everything survives the round trip
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Inputs, outputs
|
|
143
|
+
|
|
144
|
+
- **Input:** situation material (prose), held tensions, person-state
|
|
145
|
+
observations, corrections, restatements to check.
|
|
146
|
+
- **Output:** a `ScanReport` (REVIEW items per discipline), and/or a
|
|
147
|
+
rendered `audit-record` Markdown document — complete, timestamped,
|
|
148
|
+
parseable back into the full structured record.
|
|
149
|
+
|
|
150
|
+
## Worked examples
|
|
151
|
+
|
|
152
|
+
- `examples/example-1-workplace-dispute.md` — a full direct-audit
|
|
153
|
+
cycle on a neutral workplace dispute, all five disciplines, record
|
|
154
|
+
and conversational surface.
|
|
155
|
+
- `examples/example-2-lint-flags.md` — the linter catching a bad
|
|
156
|
+
audit draft, flag by flag.
|
|
157
|
+
|
|
158
|
+
*Both situations are constructed for teaching — they show the audit's
|
|
159
|
+
shape, not real findings.*
|
|
160
|
+
|
|
161
|
+
## Overlap with evasion_audit (documented, not imported)
|
|
162
|
+
|
|
163
|
+
Disciplines 1–3 mirror the ground-mapping in `evasion_audit`
|
|
164
|
+
(the probe+forge conversion): the subordinator wordlist, the
|
|
165
|
+
`HeldTension` / `PersonState` / `Correction` records are intentionally
|
|
166
|
+
identical. This package stays standalone and dependency-free, so the
|
|
167
|
+
definitions are duplicated rather than imported — the wordlists match
|
|
168
|
+
so the two packages can't silently disagree. The evidence taxonomy is
|
|
169
|
+
the same 11-tag set all three packages share, vendored here with the
|
|
170
|
+
`check_matches_engine()` fork guard.
|
|
171
|
+
|
|
172
|
+
## FAQ
|
|
173
|
+
|
|
174
|
+
**Does it need the interrogation engine?** No. Direct audit mode is
|
|
175
|
+
standalone and is the headline use. Overlay mode attaches to engine
|
|
176
|
+
phases; with no engine running, the overlay reports the record and
|
|
177
|
+
stops.
|
|
178
|
+
|
|
179
|
+
**Does it resolve anything?** Never. That's the stand-down rule, and
|
|
180
|
+
it's enforced in code, not just in prose.
|
|
181
|
+
|
|
182
|
+
**What does it cost?** Nothing. Stdlib only, no model calls, no network.
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT. See LICENSE.
|
postmo-1.0.0/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# postmo — hold the mess, don't resolve it
|
|
2
|
+
|
|
3
|
+
An audit is not a narrative. A narrative resolves tension toward a
|
|
4
|
+
single coherent story; an audit holds the actual shape of what's been
|
|
5
|
+
said — including the parts that don't resolve — and tracks it
|
|
6
|
+
accurately as it changes.
|
|
7
|
+
|
|
8
|
+
Five disciplines, enforced where they're mechanical and explicit
|
|
9
|
+
protocol where they need judgment:
|
|
10
|
+
|
|
11
|
+
1. **Hold contradictions as contradictions.** "I adored her and I'm
|
|
12
|
+
furious at her" stays two live halves — never "X, but Y", never a
|
|
13
|
+
synthesized third thing, never flagged as a problem needing
|
|
14
|
+
resolution.
|
|
15
|
+
2. **Track people as time-indexed states, not verdicts.** Tuesday-her
|
|
16
|
+
and Thursday-her are both real. Nobody is "a liar" as a fixed label.
|
|
17
|
+
3. **Type every correction: detail vs core-claim.** A fixed date doesn't
|
|
18
|
+
reopen standing claims; a withdrawn accusation is the signal.
|
|
19
|
+
4. **Never supply motive the teller didn't state.** "He tackled you
|
|
20
|
+
because he panicked" is invented, even if plausible. Observed
|
|
21
|
+
sequence only — who did what, in what order.
|
|
22
|
+
5. **Self-check for softening.** Did "betrayal" become "mistake"? Did a
|
|
23
|
+
restatement tidy the account? Did ambivalence get resolved into one
|
|
24
|
+
"real" feeling? The noticing is part of the deliverable.
|
|
25
|
+
|
|
26
|
+
No prerequisites. No model, no network — the operator (human or model)
|
|
27
|
+
supplies the material; the library enforces the discipline. The
|
|
28
|
+
mechanism is the whole product.
|
|
29
|
+
|
|
30
|
+
*(Lineage: the front-facing conversion of the `forensic-situational-audit`
|
|
31
|
+
skill from Ryan's skill-suite, MIT. What changed is documented in
|
|
32
|
+
CHANGELOG.md — the name, the "field" language, and prose rules turned
|
|
33
|
+
into checked code.)*
|
|
34
|
+
|
|
35
|
+
## What it does
|
|
36
|
+
|
|
37
|
+
Two modes:
|
|
38
|
+
|
|
39
|
+
- **Direct audit (the headline).** "Don't resolve this, just hold it."
|
|
40
|
+
The operator works a messy multi-person situation conversationally;
|
|
41
|
+
the package scans drafts for discipline violations and accumulates
|
|
42
|
+
the audit record. No engine, no interrogation.
|
|
43
|
+
- **Overlay.** Attaches to the interrogation engine's Decompose phase
|
|
44
|
+
(disciplines 2–4) and Collide phase (disciplines 1, 5 — the strict
|
|
45
|
+
enforcer: no subordinating conjunctions, no synthesized third thing).
|
|
46
|
+
The overlay never resolves, never synthesizes, never converts a
|
|
47
|
+
direct audit into an interrogation unasked.
|
|
48
|
+
|
|
49
|
+
The canonical artifact is the **audit record**: which disciplines
|
|
50
|
+
fired, held tensions, person-state sequences, typed corrections,
|
|
51
|
+
softening and motive flags — structured data with timestamps that the
|
|
52
|
+
next session can load and continue from. Machine-readable first
|
|
53
|
+
(`record.render()`), renderable conversationally
|
|
54
|
+
(`record.render_conversation()`).
|
|
55
|
+
|
|
56
|
+
## Why it works
|
|
57
|
+
|
|
58
|
+
Most "holding space" collapses into narrative smoothing — the grammar
|
|
59
|
+
does the resolving before anyone notices. The audit attacks that
|
|
60
|
+
mechanically:
|
|
61
|
+
|
|
62
|
+
- **The subordinator scan** rejects "X, but/however/which means Y" in
|
|
63
|
+
held tensions. The "but" demotes one side; the audit refuses to hold
|
|
64
|
+
a tension that's already been resolved in grammar.
|
|
65
|
+
- **The motive scan** flags causal attributions with no teller
|
|
66
|
+
attribution ("because he panicked") while passing attributed ones
|
|
67
|
+
("he said he panicked") and keeping marked guesses marked.
|
|
68
|
+
- **The softening scan** compares the teller's words against
|
|
69
|
+
restatements: downgrade wordlist ("betrayal" → "mistake"), dropped
|
|
70
|
+
strong terms, tidy-restatement phrases ("so what you're saying is"),
|
|
71
|
+
ambivalence-resolution phrases ("the real feeling is").
|
|
72
|
+
- **The stand-down rule** is schema-level: the record rejects
|
|
73
|
+
synthesis/resolution/verdict sections. An audit that resolves has
|
|
74
|
+
stopped auditing.
|
|
75
|
+
|
|
76
|
+
What the scans can't do stays explicitly operator protocol — each
|
|
77
|
+
discipline's table entry (`disciplines.py`) marks the code/operator
|
|
78
|
+
split. A fired check is a finding; a quiet check is not a clean bill
|
|
79
|
+
of health.
|
|
80
|
+
|
|
81
|
+
## Quickstart
|
|
82
|
+
|
|
83
|
+
Zero dependencies beyond Python 3.10+. Fully offline.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cd audit
|
|
87
|
+
PYTHONPATH=src python3 -m unittest discover -s tests # 55 offline tests
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Lint an audit draft:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
PYTHONPATH=src python3 examples/lint_audit.py --text examples/bad-draft.txt \
|
|
94
|
+
--restatement examples/bad-restatement.txt
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Build a record in code:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from postmo import (
|
|
101
|
+
AuditRecord, HeldTension, PersonState, Correction, scan_text,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
rec = AuditRecord(case_name="Warehouse shift dispute")
|
|
105
|
+
rec.tensions.append(HeldTension(text=(
|
|
106
|
+
"Dana says the pallet was already damaged. "
|
|
107
|
+
"Marco says it was intact.")))
|
|
108
|
+
rec.states.append(PersonState(when="2026-09-18", person="Dana",
|
|
109
|
+
state="calm, cooperative"))
|
|
110
|
+
rec.corrections.append(Correction(kind="core-claim",
|
|
111
|
+
text="Marco withdrew the claim that Dana hid the damage."))
|
|
112
|
+
rec.log(1, "enforced", "1 tension held as parallel sentences")
|
|
113
|
+
|
|
114
|
+
report = scan_text("He tackled you because he panicked.")
|
|
115
|
+
for flag in report.motive_flags:
|
|
116
|
+
print(flag.severity, ":", flag.sentence)
|
|
117
|
+
|
|
118
|
+
print(rec.render()) # the machine-readable record
|
|
119
|
+
print(rec.render_conversation()) # the human surface
|
|
120
|
+
|
|
121
|
+
# Next session:
|
|
122
|
+
from postmo import parse
|
|
123
|
+
rec2 = parse(rec.render()) # everything survives the round trip
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Inputs, outputs
|
|
127
|
+
|
|
128
|
+
- **Input:** situation material (prose), held tensions, person-state
|
|
129
|
+
observations, corrections, restatements to check.
|
|
130
|
+
- **Output:** a `ScanReport` (REVIEW items per discipline), and/or a
|
|
131
|
+
rendered `audit-record` Markdown document — complete, timestamped,
|
|
132
|
+
parseable back into the full structured record.
|
|
133
|
+
|
|
134
|
+
## Worked examples
|
|
135
|
+
|
|
136
|
+
- `examples/example-1-workplace-dispute.md` — a full direct-audit
|
|
137
|
+
cycle on a neutral workplace dispute, all five disciplines, record
|
|
138
|
+
and conversational surface.
|
|
139
|
+
- `examples/example-2-lint-flags.md` — the linter catching a bad
|
|
140
|
+
audit draft, flag by flag.
|
|
141
|
+
|
|
142
|
+
*Both situations are constructed for teaching — they show the audit's
|
|
143
|
+
shape, not real findings.*
|
|
144
|
+
|
|
145
|
+
## Overlap with evasion_audit (documented, not imported)
|
|
146
|
+
|
|
147
|
+
Disciplines 1–3 mirror the ground-mapping in `evasion_audit`
|
|
148
|
+
(the probe+forge conversion): the subordinator wordlist, the
|
|
149
|
+
`HeldTension` / `PersonState` / `Correction` records are intentionally
|
|
150
|
+
identical. This package stays standalone and dependency-free, so the
|
|
151
|
+
definitions are duplicated rather than imported — the wordlists match
|
|
152
|
+
so the two packages can't silently disagree. The evidence taxonomy is
|
|
153
|
+
the same 11-tag set all three packages share, vendored here with the
|
|
154
|
+
`check_matches_engine()` fork guard.
|
|
155
|
+
|
|
156
|
+
## FAQ
|
|
157
|
+
|
|
158
|
+
**Does it need the interrogation engine?** No. Direct audit mode is
|
|
159
|
+
standalone and is the headline use. Overlay mode attaches to engine
|
|
160
|
+
phases; with no engine running, the overlay reports the record and
|
|
161
|
+
stops.
|
|
162
|
+
|
|
163
|
+
**Does it resolve anything?** Never. That's the stand-down rule, and
|
|
164
|
+
it's enforced in code, not just in prose.
|
|
165
|
+
|
|
166
|
+
**What does it cost?** Nothing. Stdlib only, no model calls, no network.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT. See LICENSE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "postmo"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Hold a messy multi-person situation without resolving it: five audit disciplines as linter checks plus a full structured audit record."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Ryan Wesley Hitchcock" }]
|
|
13
|
+
keywords = ["audit", "contradiction", "linter", "evidence", "interrogation"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/ohpersonperson/frontface"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
testpaths = ["tests"]
|
postmo-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""postmo — hold a messy situation without resolving it.
|
|
2
|
+
|
|
3
|
+
Five audit disciplines, enforced as a linter where they're mechanical
|
|
4
|
+
and as explicit operator protocol where they need judgment:
|
|
5
|
+
|
|
6
|
+
1. Hold contradictions as contradictions (parallel sentences, never "X, but Y").
|
|
7
|
+
2. Track people as time-indexed states, never categorical verdicts.
|
|
8
|
+
3. Type every correction: detail vs core-claim.
|
|
9
|
+
4. Never supply motive or causality the teller didn't state.
|
|
10
|
+
5. Self-check every output for softened threat language, supplied
|
|
11
|
+
generous interpretations, dropped contradictions, resolved ambivalence.
|
|
12
|
+
|
|
13
|
+
Two modes: direct audit (the headline — "don't resolve this, just hold
|
|
14
|
+
it", no engine needed) and overlay on the interrogation engine's
|
|
15
|
+
Decompose/Collide phases. Either way the canonical artifact is the
|
|
16
|
+
audit record: full structured data the next session can load, with
|
|
17
|
+
timestamps. The audit never resolves — the record schema rejects
|
|
18
|
+
synthesis/resolution/verdict sections.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .disciplines import DISCIPLINES, D1, D2, D3, D4, D5, discipline_name
|
|
22
|
+
from .evidence import (
|
|
23
|
+
EvidenceError, EvidenceItem, check_matches_engine, normalize_tag, tag_list,
|
|
24
|
+
)
|
|
25
|
+
from .overlay import NEVER, attachment_for, describe_contract
|
|
26
|
+
from .record import (
|
|
27
|
+
PROTOCOL, AuditRecord, DisciplineEntry, RecordError, check_stand_down, parse,
|
|
28
|
+
utc_now,
|
|
29
|
+
)
|
|
30
|
+
from .scans import (
|
|
31
|
+
Correction, CorrectionCandidate, HeldTension, MotiveFlag, PersonState,
|
|
32
|
+
ScanError, ScanReport, SofteningFlag, VerdictWordHit,
|
|
33
|
+
detect_softening, detect_supplied_motive, find_correction_candidates,
|
|
34
|
+
find_subordinators, find_verdict_words, scan_text,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"DISCIPLINES", "D1", "D2", "D3", "D4", "D5", "discipline_name",
|
|
39
|
+
"EvidenceError", "EvidenceItem", "check_matches_engine",
|
|
40
|
+
"normalize_tag", "tag_list",
|
|
41
|
+
"NEVER", "attachment_for", "describe_contract",
|
|
42
|
+
"PROTOCOL", "AuditRecord", "DisciplineEntry", "RecordError",
|
|
43
|
+
"check_stand_down", "parse", "utc_now",
|
|
44
|
+
"Correction", "CorrectionCandidate", "HeldTension", "MotiveFlag",
|
|
45
|
+
"PersonState", "ScanError", "ScanReport", "SofteningFlag",
|
|
46
|
+
"VerdictWordHit", "detect_softening", "detect_supplied_motive",
|
|
47
|
+
"find_correction_candidates", "find_subordinators", "find_verdict_words",
|
|
48
|
+
"scan_text",
|
|
49
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""The five audit disciplines, stated once.
|
|
2
|
+
|
|
3
|
+
An audit is not a narrative: a narrative resolves tension toward a
|
|
4
|
+
single coherent story; an audit holds the actual shape of what's been
|
|
5
|
+
said — including the parts that don't resolve — and tracks it
|
|
6
|
+
accurately as it changes.
|
|
7
|
+
|
|
8
|
+
Each discipline carries its code/operator split explicitly:
|
|
9
|
+
`mechanical` is what `scans.py` checks; `operator` is what needs a
|
|
10
|
+
human or model judgment and stays protocol, not code. The split is
|
|
11
|
+
honest, not aspirational — a mechanical check that fired is a finding;
|
|
12
|
+
an operator check is a responsibility.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
D1 = 1 # hold contradictions as contradictions
|
|
18
|
+
D2 = 2 # track person-state as time-indexed, not categorical
|
|
19
|
+
D3 = 3 # distinguish detail-correction from core-claim correction
|
|
20
|
+
D4 = 4 # don't supply motive or causality the teller didn't state
|
|
21
|
+
D5 = 5 # self-check for softening and evasion — including your own
|
|
22
|
+
|
|
23
|
+
DISCIPLINES: dict[int, dict[str, str]] = {
|
|
24
|
+
D1: {
|
|
25
|
+
"name": "hold-contradictions",
|
|
26
|
+
"rule": (
|
|
27
|
+
"When two stated things are in tension, do not pick one side, "
|
|
28
|
+
"do not synthesize a third thing that subordinates one to the "
|
|
29
|
+
"other, and do not flag the tension as a problem needing "
|
|
30
|
+
"resolution. Both halves stay live. If a synthesis is true, "
|
|
31
|
+
"the teller states it — never supply it on their behalf."
|
|
32
|
+
),
|
|
33
|
+
"mechanical": (
|
|
34
|
+
"Held tensions must be parallel non-subordinating sentences "
|
|
35
|
+
"('X is true. Y is also true.') — the subordinator scan in "
|
|
36
|
+
"scans.py rejects 'X, but/however/which means Y'."
|
|
37
|
+
),
|
|
38
|
+
"operator": (
|
|
39
|
+
"Recognizing that two statements ARE in tension (the scan "
|
|
40
|
+
"checks grammar, not meaning); refusing to tidy the tension "
|
|
41
|
+
"in surrounding prose; leaving silence where no resolving "
|
|
42
|
+
"sentence is needed."
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
D2: {
|
|
46
|
+
"name": "time-indexed-person-states",
|
|
47
|
+
"rule": (
|
|
48
|
+
"People are not 'good' or 'bad', 'safe' or 'dangerous' as fixed "
|
|
49
|
+
"labels — they are in different states at different points, and "
|
|
50
|
+
"all of those states are real simultaneously. Summarize "
|
|
51
|
+
"sequences of states, never verdicts on persons."
|
|
52
|
+
),
|
|
53
|
+
"mechanical": (
|
|
54
|
+
"PersonState requires when + person + state, all non-empty; "
|
|
55
|
+
"the verdict-word scan flags permanent-label language "
|
|
56
|
+
"('he is a liar') for operator review."
|
|
57
|
+
),
|
|
58
|
+
"operator": (
|
|
59
|
+
"Resisting the pull to declare one state 'the mask' and another "
|
|
60
|
+
"'the truth'; keeping the sequence ordered and complete."
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
D3: {
|
|
64
|
+
"name": "correction-typing",
|
|
65
|
+
"rule": (
|
|
66
|
+
"Detail correction: a particular fact is revised (a time, an "
|
|
67
|
+
"object, a peripheral sequence detail) — it does not touch any "
|
|
68
|
+
"core claim standing elsewhere. Core-claim correction: a "
|
|
69
|
+
"decision, boundary, conclusion, or central assertion is "
|
|
70
|
+
"revised. Never let a detail correction trigger re-litigation "
|
|
71
|
+
"of an uncorrected core claim. If ambiguous which kind, ask "
|
|
72
|
+
"rather than guess."
|
|
73
|
+
),
|
|
74
|
+
"mechanical": (
|
|
75
|
+
"Correction records accept only 'detail' or 'core-claim'; the "
|
|
76
|
+
"correction-candidate scan flags correction-like sentences "
|
|
77
|
+
"('actually…', 'no wait…', 'to be clear…') that haven't been "
|
|
78
|
+
"typed yet."
|
|
79
|
+
),
|
|
80
|
+
"operator": (
|
|
81
|
+
"Typing ambiguous corrections (ask, don't guess); holding the "
|
|
82
|
+
"line that a detail fix does not reopen standing core claims."
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
D4: {
|
|
86
|
+
"name": "no-supplied-motive",
|
|
87
|
+
"rule": (
|
|
88
|
+
"Track what happened and what was said — not why, unless the "
|
|
89
|
+
"teller assigns the why. 'He tackled you because he panicked' "
|
|
90
|
+
"is an invented motive, even if plausible. State the observed "
|
|
91
|
+
"sequence: who did what, in what order. A directly requested "
|
|
92
|
+
"read on motive is a flagged guess ('if I had to guess…'), "
|
|
93
|
+
"never narrated as settled fact."
|
|
94
|
+
),
|
|
95
|
+
"mechanical": (
|
|
96
|
+
"The motive scan flags causal/motive attributions ('because he "
|
|
97
|
+
"panicked', 'driven by', 'in order to') that carry no "
|
|
98
|
+
"attribution marker ('he said', 'according to', quotes) and no "
|
|
99
|
+
"explicit guess-flag."
|
|
100
|
+
),
|
|
101
|
+
"operator": (
|
|
102
|
+
"The final call on whether a flagged attribution was actually "
|
|
103
|
+
"supplied by the teller in surrounding context the scan can't "
|
|
104
|
+
"see; keeping flagged guesses flagged."
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
D5: {
|
|
108
|
+
"name": "softening-self-check",
|
|
109
|
+
"rule": (
|
|
110
|
+
"Before finalizing, check: did a threat/betrayal/harm get "
|
|
111
|
+
"gentler language than the teller used? Did a generous "
|
|
112
|
+
"interpretation get supplied unasked? Did a restatement drop a "
|
|
113
|
+
"contradiction or tidy it? Did ambivalence get resolved into "
|
|
114
|
+
"one 'real' feeling? Did a direct question get answered "
|
|
115
|
+
"evasively? The noticing is part of the deliverable. Also runs "
|
|
116
|
+
"outward: name the teller's own smoothing once, plainly, "
|
|
117
|
+
"without diagnosing — don't press, don't repeat if unpicked."
|
|
118
|
+
),
|
|
119
|
+
"mechanical": (
|
|
120
|
+
"The softening scan compares original vs restatement: "
|
|
121
|
+
"downgrade wordlist ('betrayal' → 'mistake'), dropped strong "
|
|
122
|
+
"terms, tidy-restatement phrases ('so what you're saying is'), "
|
|
123
|
+
"ambivalence-resolution phrases ('the real feeling is')."
|
|
124
|
+
),
|
|
125
|
+
"operator": (
|
|
126
|
+
"Generous-interpretation detection; evasive-answer detection; "
|
|
127
|
+
"judging whether a flagged downgrade is a genuine softening or "
|
|
128
|
+
"the teller's own word choice."
|
|
129
|
+
),
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def discipline_name(n: int) -> str:
|
|
135
|
+
"""Short name for discipline n (1-5)."""
|
|
136
|
+
if n not in DISCIPLINES:
|
|
137
|
+
raise ValueError(f"Unknown discipline: {n!r}. Valid: 1-5.")
|
|
138
|
+
return DISCIPLINES[n]["name"]
|