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 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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"]