smd-engine 0.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. smd_engine/__init__.py +9 -0
  2. smd_engine/apply.py +247 -0
  3. smd_engine/assets/data-model.md +266 -0
  4. smd_engine/assets/lenses/README.md +90 -0
  5. smd_engine/assets/lenses/design-chain.md +116 -0
  6. smd_engine/assets/lenses/fliamer-diamond.md +113 -0
  7. smd_engine/assets/lenses/khaprov-levels.md +104 -0
  8. smd_engine/assets/lenses/khaprov-ontology.md +110 -0
  9. smd_engine/assets/lenses/scamper-check.md +83 -0
  10. smd_engine/assets/lenses/schukin-boundaries.md +108 -0
  11. smd_engine/assets/lenses/schukin-interface-tax.md +102 -0
  12. smd_engine/assets/lenses/triz-contradictions.md +95 -0
  13. smd_engine/assets/procedures/README.md +38 -0
  14. smd_engine/assets/procedures/field-result.md +56 -0
  15. smd_engine/assets/procedures/functional-plan.md +182 -0
  16. smd_engine/assets/procedures/ingest-transcript.md +118 -0
  17. smd_engine/assets/procedures/positional-construction.md +188 -0
  18. smd_engine/assets/procedures/problematization.md +184 -0
  19. smd_engine/assets/procedures/qualification-pass.md +226 -0
  20. smd_engine/assets/procedures/reflexive-protocol.md +170 -0
  21. smd_engine/assets/procedures/small-situation.md +222 -0
  22. smd_engine/assets/procedures/solo-session.md +174 -0
  23. smd_engine/assets/procedures/theses-to-board.md +91 -0
  24. smd_engine/assets/procedures/visionary-tract.md +266 -0
  25. smd_engine/assets/profiles/classic-two-boards.yaml +67 -0
  26. smd_engine/assets/profiles/program-three-boards.yaml +92 -0
  27. smd_engine/assets/roles/README.md +49 -0
  28. smd_engine/assets/roles/actors.md +72 -0
  29. smd_engine/assets/roles/game-technician.md +63 -0
  30. smd_engine/assets/roles/methodologist.md +100 -0
  31. smd_engine/assets/roles/researcher.md +44 -0
  32. smd_engine/assets/roles/visionary.md +75 -0
  33. smd_engine/cli.py +811 -0
  34. smd_engine/compare.py +321 -0
  35. smd_engine/eval.py +140 -0
  36. smd_engine/migrate.py +179 -0
  37. smd_engine/pack.py +185 -0
  38. smd_engine/profile.py +583 -0
  39. smd_engine/records.py +137 -0
  40. smd_engine/render/__init__.py +12 -0
  41. smd_engine/render/assets/frames.css +42 -0
  42. smd_engine/render/assets/frames.js +46 -0
  43. smd_engine/render/assets/graph.css +31 -0
  44. smd_engine/render/assets/graph.js +246 -0
  45. smd_engine/render/frames.py +292 -0
  46. smd_engine/render/frames_page.py +184 -0
  47. smd_engine/render/frames_text.py +106 -0
  48. smd_engine/render/graph.py +342 -0
  49. smd_engine/render/graph_ui.py +58 -0
  50. smd_engine/render/html.py +138 -0
  51. smd_engine/render/index.py +303 -0
  52. smd_engine/render/layout.py +301 -0
  53. smd_engine/render/light.py +124 -0
  54. smd_engine/render/markdown.py +157 -0
  55. smd_engine/render/mermaid.py +57 -0
  56. smd_engine/render/sessionflow.py +223 -0
  57. smd_engine/render/svg.py +544 -0
  58. smd_engine/render/text.py +284 -0
  59. smd_engine/render/vault.py +322 -0
  60. smd_engine/revise.py +127 -0
  61. smd_engine/schemas/__init__.py +0 -0
  62. smd_engine/schemas/board_patch.schema.json +81 -0
  63. smd_engine/schemas/board_set.schema.json +304 -0
  64. smd_engine/schemas/source_manifest.schema.json +40 -0
  65. smd_engine/schemas/taxonomies.schema.json +32 -0
  66. smd_engine/screen.py +71 -0
  67. smd_engine/sources.py +145 -0
  68. smd_engine/validate/__init__.py +205 -0
  69. smd_engine/validate/context.py +99 -0
  70. smd_engine/validate/hints.py +301 -0
  71. smd_engine/validate/hypotheses.py +49 -0
  72. smd_engine/validate/manifest.py +30 -0
  73. smd_engine/validate/ontology.py +43 -0
  74. smd_engine/validate/signoffs.py +130 -0
  75. smd_engine/validate/structure.py +294 -0
  76. smd_engine/validate/taxonomies.py +74 -0
  77. smd_engine/validate/theses.py +73 -0
  78. smd_engine-0.3.7.dist-info/METADATA +178 -0
  79. smd_engine-0.3.7.dist-info/RECORD +82 -0
  80. smd_engine-0.3.7.dist-info/WHEEL +4 -0
  81. smd_engine-0.3.7.dist-info/entry_points.txt +2 -0
  82. smd_engine-0.3.7.dist-info/licenses/LICENSE +21 -0
smd_engine/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """Records, checks and renderings for SMD-style thinking sessions."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("smd-engine") # the installed package, as in pyproject.toml
7
+ except PackageNotFoundError: # a source tree that is not installed
8
+ __version__ = "0+unknown"
9
+ SCHEMA_VERSION = "0.3"
smd_engine/apply.py ADDED
@@ -0,0 +1,247 @@
1
+ """Apply a board patch: a decision recorded as additions, field changes,
2
+ removals and state changes, giving the next sub-version of the same cycle."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import copy
7
+ from typing import Any
8
+
9
+ # Board lists a patch may add to, in the order of the board schema.
10
+ ADDABLE = (
11
+ "elements",
12
+ "relations",
13
+ "transitions",
14
+ "qualifications",
15
+ "theses",
16
+ "hypotheses",
17
+ "ontology",
18
+ "signoffs",
19
+ )
20
+ # Board lists whose items have an id that `set` and `remove` may name.
21
+ WITH_ID = ("elements", "qualifications", "theses", "hypotheses", "ontology")
22
+ # Item fields that name other items: lists dropped when they become empty...
23
+ OPTIONAL_LISTS = ("in", "feeds", "about", "targets", "raises", "qualifications")
24
+ # ...lists the schema requires to be non-empty, left empty for the validator to
25
+ # report, so that a removal never deletes a decision silently...
26
+ REQUIRED_LISTS = ("valid_for",)
27
+ # ...and single ids, deleted.
28
+ SCALARS = ("seen_from", "performed_by", "cooperation", "test")
29
+
30
+
31
+ class PatchError(ValueError):
32
+ """The patch does not fit the board: a wrong board or an unknown id."""
33
+
34
+
35
+ def apply_patch(board: dict[str, Any], patch: dict[str, Any]) -> dict[str, Any]:
36
+ """The board with the patch applied; board is not changed.
37
+
38
+ Applies add, set, board_notes, remove, sources, state and title in that
39
+ order, then sets state.version and appends the patch id to `applied`.
40
+ `set` and `remove` address a transition by its number `n` as it is after
41
+ `add`, before this patch renumbers. Raises PatchError for a patch made for
42
+ another board or naming an unknown id or transition number, and ValueError
43
+ for a patch that was already applied.
44
+ """
45
+ if patch["applies_to"] != board["id"]:
46
+ raise PatchError(
47
+ f"patch {patch['id']} applies to {patch['applies_to']}, not {board['id']}"
48
+ )
49
+ if patch["id"] in board.get("applied", []):
50
+ raise ValueError(f"patch {patch['id']} is already applied to {board['id']}")
51
+ new = copy.deepcopy(board)
52
+ add = patch.get("add", {})
53
+ for field in ADDABLE:
54
+ if add.get(field):
55
+ new.setdefault(field, []).extend(copy.deepcopy(add[field]))
56
+ for change in patch.get("set", []):
57
+ _set(new, change)
58
+ # Lines appended to the board's notes, joined to it by "\n": the board_notes
59
+ # text, then one line per removal.
60
+ lines = []
61
+ if patch.get("board_notes", "").strip():
62
+ lines.append(patch["board_notes"].rstrip())
63
+ # An item id (str) or a transition number (int) -> reason, in patch order.
64
+ removed = {
65
+ r["id"] if "id" in r else r["n"]: r["reason"] for r in patch.get("remove", [])
66
+ }
67
+ for key in removed:
68
+ if isinstance(key, int):
69
+ if _transition(new, key) is None:
70
+ raise PatchError(f"remove: unknown transition {key}")
71
+ elif _find(new, key) is None:
72
+ raise PatchError(f"remove: unknown id '{key}'")
73
+ if removed:
74
+ ids = {k for k in removed if isinstance(k, str)}
75
+ numbers = {k for k in removed if isinstance(k, int)}
76
+ dropped = _remove(new, ids, numbers)
77
+ for key, why in removed.items():
78
+ if isinstance(key, int):
79
+ lines.append(f"{patch['id']}: removed transition {key}: {why}")
80
+ else:
81
+ head = f"{patch['id']}: removed {key}: {why}"
82
+ lines.append("; ".join([head] + dropped.get(key, [])))
83
+ if lines:
84
+ new["notes"] = "\n".join(([new["notes"]] if new.get("notes") else []) + lines)
85
+ for sid in add.get("sources", []):
86
+ if sid not in new.setdefault("sources", []):
87
+ new["sources"].append(sid)
88
+ _state(new, patch.get("state", {}))
89
+ if "title" in patch:
90
+ new["title"] = patch["title"]
91
+ new.setdefault("state", {})["version"] = str(patch["version"])
92
+ if "applied" not in new:
93
+ new = _with_applied(new)
94
+ new["applied"].append(patch["id"])
95
+ return new
96
+
97
+
98
+ def _with_applied(board: dict[str, Any]) -> dict[str, Any]:
99
+ """The board with an empty `applied`, placed before the elements as in the schema."""
100
+ out: dict[str, Any] = {}
101
+ for key, value in board.items():
102
+ if key == "elements":
103
+ out["applied"] = []
104
+ out[key] = value
105
+ out.setdefault("applied", [])
106
+ return out
107
+
108
+
109
+ def _find(board: dict[str, Any], item_id: str) -> dict[str, Any] | None:
110
+ for field in WITH_ID:
111
+ for item in board.get(field, []):
112
+ if item.get("id") == item_id:
113
+ found: dict[str, Any] = item
114
+ return found
115
+ return None
116
+
117
+
118
+ def _transition(board: dict[str, Any], n: int) -> dict[str, Any] | None:
119
+ for item in board.get("transitions", []):
120
+ if item.get("n") == n:
121
+ found: dict[str, Any] = item
122
+ return found
123
+ return None
124
+
125
+
126
+ def _set(board: dict[str, Any], change: dict[str, Any]) -> None:
127
+ """Merge the change into its item, the record of an id or the transition
128
+ numbered `n`: a mapping merges into a mapping one level deep, key by key;
129
+ null deletes a field or a key; lists and scalars replace."""
130
+ if "id" in change:
131
+ item = _find(board, change["id"])
132
+ if item is None:
133
+ raise PatchError(f"set: unknown id '{change['id']}'")
134
+ else:
135
+ item = _transition(board, change["n"])
136
+ if item is None:
137
+ raise PatchError(f"set: unknown transition {change['n']}")
138
+ for key, value in change.items():
139
+ if key in ("id", "n"):
140
+ continue
141
+ if value is None:
142
+ item.pop(key, None)
143
+ elif isinstance(value, dict) and isinstance(item.get(key), dict):
144
+ for sub, sub_value in value.items():
145
+ if sub_value is None:
146
+ item[key].pop(sub, None)
147
+ else:
148
+ item[key][sub] = copy.deepcopy(sub_value)
149
+ else:
150
+ item[key] = copy.deepcopy(value)
151
+
152
+
153
+ def _gone(value: Any, gone: set[str]) -> bool:
154
+ """Whether a field value names a removed item; a value of a wrong shape
155
+ does not, and is left for the schema check after application."""
156
+ return isinstance(value, str) and value in gone
157
+
158
+
159
+ def _names(link: dict[str, Any], gone: set[str]) -> bool:
160
+ return _gone(link.get("from"), gone) or _gone(link.get("to"), gone)
161
+
162
+
163
+ def _n(transition: dict[str, Any]) -> int:
164
+ n = transition.get("n")
165
+ return n if isinstance(n, int) else 0
166
+
167
+
168
+ def _remove(
169
+ board: dict[str, Any], gone: set[str], numbers: set[int]
170
+ ) -> dict[str, list[str]]:
171
+ """Drop the items, the transitions numbered `numbers` and every link that
172
+ names the items; return, per removed id, the sign-offs dropped with it."""
173
+ for field in WITH_ID:
174
+ if field in board:
175
+ board[field] = [x for x in board[field] if not _gone(x.get("id"), gone)]
176
+ if "relations" in board:
177
+ board["relations"] = [r for r in board["relations"] if not _names(r, gone)]
178
+ if "transitions" in board:
179
+ kept = [
180
+ t
181
+ for t in sorted(board["transitions"], key=_n)
182
+ if not _names(t, gone) and _n(t) not in numbers
183
+ ]
184
+ # Transitions run 1..n; the ones after a dropped transition move up.
185
+ for n, tr in enumerate(kept, 1):
186
+ tr["n"] = n
187
+ board["transitions"] = kept
188
+ for field in WITH_ID:
189
+ for item in board.get(field, []):
190
+ _unlink(item, gone)
191
+ if _gone(board.get("reflected_from"), gone):
192
+ del board["reflected_from"]
193
+ dropped: dict[str, list[str]] = {}
194
+ kept_signoffs = []
195
+ for s in board.get("signoffs", []):
196
+ target = s.get("target")
197
+ if not _gone(target, gone):
198
+ kept_signoffs.append(s)
199
+ continue
200
+ who = ", ".join(str(s[k]) for k in ("by", "date") if s.get(k))
201
+ dropped.setdefault(target, []).append(
202
+ f"dropped sign-off {s.get('function')} {target}"
203
+ + (f" ({who})" if who else "")
204
+ )
205
+ if "signoffs" in board:
206
+ board["signoffs"] = kept_signoffs
207
+ state = board.get("state", {})
208
+ if isinstance(state.get("open"), list):
209
+ state["open"] = [o for o in state["open"] if not _gone(o, gone)]
210
+ return dropped
211
+
212
+
213
+ def _unlink(item: dict[str, Any], gone: set[str]) -> None:
214
+ for key in OPTIONAL_LISTS + REQUIRED_LISTS:
215
+ if isinstance(item.get(key), list):
216
+ item[key] = [x for x in item[key] if not _gone(x, gone)]
217
+ if not item[key] and key in OPTIONAL_LISTS:
218
+ del item[key]
219
+ for key in SCALARS:
220
+ if _gone(item.get(key), gone):
221
+ del item[key]
222
+ if isinstance(item.get("subjects"), list):
223
+ item["subjects"] = [
224
+ s
225
+ for s in item["subjects"]
226
+ if not (isinstance(s, dict) and _gone(s.get("position"), gone))
227
+ ]
228
+ if not item["subjects"]:
229
+ del item["subjects"]
230
+ move = item.get("move")
231
+ if isinstance(move, dict) and isinstance(move.get("from"), list):
232
+ # An empty `from` stays for the validator to report.
233
+ move["from"] = [x for x in move["from"] if not _gone(x, gone)]
234
+
235
+
236
+ def _state(board: dict[str, Any], change: dict[str, Any]) -> None:
237
+ if not change:
238
+ return
239
+ state = board.setdefault("state", {})
240
+ for key in ("accepted", "open"):
241
+ if change.get(key):
242
+ state[key] = state.get(key, []) + list(change[key])
243
+ if change.get("remove_open") and "open" in state:
244
+ state["open"] = [o for o in state["open"] if o not in change["remove_open"]]
245
+ for key in ("next_move", "difficulty"):
246
+ if key in change:
247
+ state[key] = change[key]
@@ -0,0 +1,266 @@
1
+ # Data model (schema v0.3)
2
+
3
+ Two record types, `board_set` and `source_manifest` (schema 0.3), a taxonomy file (`taxonomies`, schema 0.1) and a patch (`board_patch`, schema 0.1). YAML is the authoring format; the JSON Schemas are in `src/smd_engine/schemas/`, one file per record type.
4
+
5
+ The validator accepts only the current schema version. A record with another `schema_version` is reported as one error, `schema 0.2: run smd migrate`, and its fields are not checked (see Migration below).
6
+
7
+ ## board_set: one cycle of a three-board screen
8
+
9
+ Top level: `id`, `case`, `title`, `date`, `cycle` (1, 2, ...), `revises` (id of the previous cycle), `reflected_from`, `status` (`draft` | `extracted` | `verified`, the last needs `checked_by`), `sources` (manifest ids), `lineage`, `profile`, `run`, `applied`, `elements`, `relations`, `transitions`, `qualifications`, `theses`, `hypotheses`, `ontology`, `signoffs`, `state`.
10
+
11
+ - `profile`: the method profile the record follows, a string; `program-three-boards` when absent. The profile sets the boards, kinds, relation kinds and hints (see Profiles below); an unknown profile is an error.
12
+ - `run`: the procedure run that produced the record: `{procedure, version, model, date}`; `procedure` (a procedure id) is required. `model` is free text, what the runner reports about itself. Each procedure writes it at hand-over and replaces an earlier one; `smd eval` compares two records (`cases/example/golden/README.md`).
13
+ - `applied`: the ids of the patches applied to the record, in order (`smd apply`, see Patches below).
14
+
15
+ `reflected_from` is the position element a new cycle was made from. A cycle is a reflexive exit, not a time slice: the group leaves the actor's position, describes the past cycle and projects the next one (GP1975; `docs/method/reconciliation.md` section 4).
16
+
17
+ ### Boards
18
+
19
+ The boards of the default profile, `program-three-boards`; `classic-two-boards` has `od` and `oo` only.
20
+
21
+ | `board` | Board | Content |
22
+ |---|---|---|
23
+ | `od` | org-activity (оргдеятельностная) | positions and their links, places for others, goals |
24
+ | `oo` | object (объектная) | frames, functions, models, concepts, standards |
25
+ | `s` | situation (ситуационная), `half: small \| big` | the group's own practice; the wider system of activity |
26
+
27
+ ### Elements
28
+
29
+ `id`, `board`, `kind`, `label` (as said or written, verbatim) and optional fields:
30
+
31
+ | Field | Meaning |
32
+ |---|---|
33
+ | `short` | screen label, about 40 characters; the screen draws it, the full `label` stays in frame texts, tables and the details panel. Without it, a label over 60 characters is cut on the screen with «…» |
34
+ | `fillers` | roles only (`position`, `place_for_others`, `gap`): the actors that can occupy the role, e.g. `[государство, регион, фонд]`. Roles go on the boards, actors in `fillers` (`docs/method/roles-and-actors.md`) |
35
+ | `detail` | a table behind the element: `title`, `columns`, `rows` (each row as long as `columns`), `keys`. Shown in the frames panel, the HTML tables and the Markdown digest, never on the boards; for a role with inner structure, e.g. stakeholders by tier. `keys` binds a column to a taxonomy, `{tier: tier}`: each cell of the column holds values of that taxonomy, several separated by `;` |
36
+ | `half` | small or big; required on a board with halves (the situation board) and only there |
37
+ | `frame_state` | `old` or `new` (frames only; new frames are drawn dashed) |
38
+ | `of` | gaps only: the block of a named scheme the gap is missing from: `product`, `material`, `means`, `method`, `function`, `position` (GP-PP) |
39
+ | `scheme` | gaps only: a free id of the named scheme that `of` refers to, e.g. `gp-pp-activity` |
40
+ | `route` | `smd` or `triz`: the line of work the item came from; theses carry it too |
41
+ | `tags` | taxonomy id to the values the item takes in it, `{tier: [t1, t2]}`; checked against the taxonomy file. Theses, hypotheses and ontology records carry it too |
42
+ | `seen_from` | element id of the position the item is seen from |
43
+ | `performed_by` | functions only: the position or place for others that performs the function; the viewpoint stays in `seen_from` |
44
+ | `in` | containers, e.g. a function inside a frame |
45
+ | `epistemic` | `stated`, `hypothesis`, `unverified`, `checked` |
46
+ | `voice` | who introduced it, from the thesis voices: `group`, `participant`, `moderator`, `expert`, `organisers`, `ai` |
47
+ | `voice_note` | the old voice text that `smd migrate` replaced with `participant`, kept as written |
48
+ | `lineage` | origin tags, e.g. `earlier-concept`; used by the clean room and highlighted |
49
+ | `cites` | `source-id`, or with an address: `#t=hh:mm:ss` (time), `#p=page`, `#u=n` (turn n of a dialogue log with no timecodes), `#s=section`, `#l=line` (plain-text sources), `#r=n&t=hh:mm:ss` (recording n of a file that holds several recordings, each with its own timecodes; recordings counted from 1 in file order) |
50
+ | `move` | the structural move that produced the element (below) |
51
+ | `lens` | id of the lens (`lenses/<id>.md`) whose questions produced the element; theses carry it too |
52
+ | `geom` | `[x, y, w, h]` as fractions of the screen; overrides automatic layout |
53
+
54
+ Kinds of the default profile (`classic-two-boards` has `position`, `reflexive_position`, `place_for_others`, `goal`, `frame`, `function`, `model`, `concept`, `standard`, `norm`, `cooperation`, `gap`, `note`, `other`):
55
+ - `entry`, `position`, `place_for_others`, `goal`;
56
+ - `frame`, `function`, `model`, `concept`, `standard`, `cooperation` (the construction through which several positions build one common object, GP1981);
57
+ - `normality_boundary`, `signal`, `garland`, `fact`, `situation`; `vision`: an orienting picture from a visionary's published work (an outside author), on the big half, citing that work (Girenko, inter-module task 4: "your visionary");
58
+ - `gap`, `note`, `other`.
59
+
60
+ The `seen_from` rule follows the organisers' criterion that nothing lies on the boards without a position. It also records the per-position subjects (предметы) of one object.
61
+
62
+ ### Moves
63
+
64
+ ```yaml
65
+ move: {op: glue, from: [f-new], via: "accounting: method, standards, professions", status: candidate}
66
+ ```
67
+
68
+ A thesis can carry a move too; there `from` may name theses as well as elements.
69
+
70
+ `op` is one of the four structural operations of Zharnikov (2026), *Invention as a Structural Operation*, doi:10.5281/zenodo.21653102:
71
+
72
+ | op | Operation | On a board |
73
+ |---|---|---|
74
+ | `adjoin` | adjoin a dimension (α) | add an axis the picture held fixed: time, observer, scale |
75
+ | `rescale` | rescale or requantize (ρ) | change grain: one deal, a flow of deals, rules of the field |
76
+ | `respecify` | re-topologize or re-specify (τ) | change admissible links, or identify two elements kept apart |
77
+ | `glue` | glue across domains (γ) | build on a shared substructure with another domain |
78
+
79
+ `status` is `candidate`, `accepted` or `rejected`. Candidates are listed as hints until the group decides.
80
+
81
+ ### Relations, transitions, qualifications, state
82
+
83
+ - **Relations** (`shift`, `flow`, `produces`, `uses`, `belongs`, `opposes`, `refers`, `represents`, `other`, the `relations` of both profiles) link elements, normally within a board. `shift` links two positions; `represents` goes from a position to its representation of an object. Relations and transitions take an optional `voice` (e.g. AI), so links an AI drew stay visible as such.
84
+ - **Transitions** are numbered 1..n and move between boards. They draw the trajectory of the group's thinking.
85
+ - **Qualifications** `K1..Kn`: `targets`, `text`, `short` (the legend's text when given), `by`, `voice` (who wrote it, e.g. AI drafting for the methodologist), `status` (`open` | `partly` | `closed`), `raised_in`, `closed_in` (cycle numbers).
86
+ - **State** is the one-line state after a cycle: `version`, `difficulty` (what failed in the previous cycle), `accepted`, `open`, `next_move`.
87
+
88
+ ### Theses (ingestion, step 2)
89
+
90
+ A thesis is a SessionFlow thesis card (frame 03) with the belt and kind of the classic method. Ingestion turns a transcript into theses, and theses into board updates.
91
+
92
+ ```yaml
93
+ - {id: T3, claim: "Nobody on the board carries the risk of unsold dough.", voice: ai,
94
+ status: ai_variant, belt: m, kind: problem, feeds: [gap-risk],
95
+ move: {op: adjoin, from: [f-new], via: "time: day-to-day demand", status: candidate}}
96
+ ```
97
+
98
+ | Field | Meaning |
99
+ |---|---|
100
+ | `id` | `T1..Tn` |
101
+ | `claim`, `grounds`, `illustration`, `consequence`, `next_move` | the card: Утверждение, Обоснование, Иллюстрация, Следствие, Следующий ход |
102
+ | `voice` | `group`, `participant`, `moderator`, `expert`, `organisers`, `ai`; no speaker names |
103
+ | `status` | the six SessionFlow labels, below |
104
+ | `belt` | `md` thought-action (мД), `mk` thought-communication (М-К), `m` pure thinking (М) (GP1987) |
105
+ | `kind` | `statement`, `difficulty`, `task`, `problem`, `question` (GP-PP, GP-Guide9) |
106
+ | `feeds` | board elements the thesis produced or changed |
107
+ | `of` | for a problem: the scheme block it is tied to, as for gaps |
108
+ | `scheme` | for a problem: the named scheme `of` refers to, as for gaps |
109
+ | `route`, `tags` | as on elements |
110
+ | `raises` | K ids the thesis raised, e.g. an organisers' remark that became a qualification |
111
+ | `about` | board elements the thesis reads or comments on without changing them (`feeds` is for elements it produced or changed) |
112
+ | `move` | the structural move the group made in its talk (`accepted`) or the model's reading of it (`candidate`) |
113
+ | `cites` | source address, `s3#t=00:08:10` (Источник) |
114
+
115
+ | `status` | SessionFlow label | Fits voice |
116
+ |---|---|---|
117
+ | `voiced` | Прозвучало в группе | group, participant |
118
+ | `assumption` | Предположение участников | group, participant |
119
+ | `proposal` | Предложение модератора / эксперта | moderator, expert, organisers |
120
+ | `ai_variant` | Вариант формулировки от ИИ | ai |
121
+ | `refined` | Уточнено группой после записи | any; needs an `accept_thesis` sign-off |
122
+ | `open` | Оставляем открытым | any |
123
+
124
+ Only the group's decision raises a status: an AI variant becomes `refined` through a sign-off, never by editing the label.
125
+
126
+ ### Hypotheses (field tests)
127
+
128
+ The inter-module form: what we assume, how we test it, what we got, what it changes. The field gives the result; the group accepts it by a sign-off.
129
+
130
+ | Field | Meaning |
131
+ |---|---|
132
+ | `id` | `H1..Hn` |
133
+ | `assumes`, `action`, `observe`, `revise_if` | что предполагаем, каким действием проверяем, что будем наблюдать, какой результат заставит пересмотреть |
134
+ | `status` | `untested`, `testing`, `confirmed`, `refuted` |
135
+ | `result`, `changes` | what the test gave; what it changes on the boards |
136
+ | `targets`, `qualifications` | element ids and K ids it is about |
137
+ | `artefacts` | the field artefacts the result rests on: manifest ids of kind `field_artefact` |
138
+ | `raised_in`, `resolved_in` | cycle numbers |
139
+ | `voice` | who formulated it; AI-drafted K and H are listed as a hint until a human keeps or drops them |
140
+ | `lens` | the lens behind it, as on elements; ontology records take `lens` too |
141
+ | `tags` | as on elements; ontology records take `tags` too |
142
+
143
+ A result (`confirmed` or `refuted`) needs `result`, `artefacts`, `resolved_in` and an `accept_result` sign-off.
144
+
145
+ ### Ontology (step 3)
146
+
147
+ An ontological picture is valid for a chosen set of positions, and is checked for realizability in activity, not for truth (GP1981; `docs/method/reconciliation.md` section 2).
148
+
149
+ | Field | Meaning |
150
+ |---|---|
151
+ | `id` | `O1..On` |
152
+ | `object` | the ontological object |
153
+ | `valid_for` | the positions the picture is valid for; places for others (positions not yet taken) may be named |
154
+ | `subjects` | per-position subjects (предметы): `{position, subject}` |
155
+ | `cooperation` | the `cooperation` element through which the common object is built |
156
+ | `competes` | what competes with what (Khaprov's ontology test) |
157
+ | `aspects` | the object taken apart, aspect name to text; the usual ones are `activity`, `carrier`, `ownership`, `management` and `separation` (how they come apart), others may be added |
158
+ | `test` | the hypothesis that tests realizability |
159
+
160
+ ### Sign-offs (interface tax)
161
+
162
+ A human sign-off point must state its function (Schukin, interface tax: a touchpoint with no function is old paper-forwarding; one with a function cannot be removed without losing verifiability).
163
+
164
+ ```yaml
165
+ signoffs:
166
+ - {function: close_k, target: K2, by: group, cycle: 2, date: "2026-01-17", note: "..."}
167
+ ```
168
+
169
+ | `function` | `target` | Licenses |
170
+ |---|---|---|
171
+ | `close_k` | a qualification | `status: closed` |
172
+ | `partly_k` | a qualification | `status: partly` (part of its request is answered; say which in `note`) |
173
+ | `reopen_k` | a qualification | `status: open` again, after it was closed (the group lifts a closing) |
174
+ | `accept_move` | an element or thesis with a move | `move.status: accepted` |
175
+ | `reject_move` | an element or thesis with a move | `move.status: rejected` |
176
+ | `accept_thesis` | a thesis | `status: refined` |
177
+ | `accept_result` | a hypothesis | `status: confirmed` or `refuted` |
178
+ | `verify_record` | none | record `status: verified` |
179
+
180
+ `by` is a person or the group, never an AI voice. Optional: `date`, `cycle`, `note` (the grounds looked at), `cites`.
181
+
182
+ ## source_manifest: raw materials, listed not copied
183
+
184
+ `case`, `location`, `sources[]` with `id`, `title`, `kind` (`transcript`, `slides`, `handout`, `analysis`, `prompt`, `correspondence`, `team_doc`, `external`, `field_artefact`, `decision_log`: the owner's or group's written decisions, cited by theses and sign-offs that rest on them), `origin` (`program` | `team` | `external`), `author`, `date`, `file` or `url`, `sha256`, `lines` (the line count the `sha256` covers), `lineage`, `clean_room` (`allow` | `deny`), `personal_data`, `note`.
185
+
186
+ `smd sources --clean-room --deny-lineage <tags>` splits sources into allowed and denied for a run that must not see a given lineage.
187
+
188
+ `smd sources <manifest> --folder F --refresh [ids]` recomputes `sha256` for the named sources (default: every `decision_log`) from the files in F and edits only those `sha256:` lines in the manifest text (comments and formatting stay; a source with a `file:` line and no `sha256:` gets one); file names are matched after Unicode NFC normalisation.
189
+
190
+ ## taxonomies: the values of tags
191
+
192
+ ```yaml
193
+ record: taxonomies
194
+ schema_version: "0.1"
195
+ case: mipt-transfer
196
+ taxonomies:
197
+ tier:
198
+ - {id: t1, ru: "владелец и семья", en: "owner and family"}
199
+ - {id: t2, ru: "сотрудники", en: "employees"}
200
+ ```
201
+
202
+ `smd validate --taxonomies <file>` checks every `tags` value and every cell of a column bound by `detail.keys` against the file: an unknown taxonomy or value is an error naming the item and the value. In a bound cell, values are separated by `;` and spaces around them are trimmed; an empty cell is allowed. Without the file, a record that uses `tags` or `detail.keys` gets one warning. `smd publish --taxonomies <file>` validates with it; `smd pack --taxonomies <file>` carries it in the pack.
203
+
204
+ ## Patches: a decision as a record
205
+
206
+ A change to the current cycle is a patch; a new cycle is `smd revise`. A patch (`*.patch.yaml`, `record: board_patch`) names the board it applies to and lists what the decision changes:
207
+
208
+ ```yaml
209
+ record: board_patch
210
+ schema_version: "0.1"
211
+ id: bakeries-c2-1
212
+ applies_to: bakeries-c2
213
+ version: "0.2.1"
214
+ add:
215
+ elements:
216
+ - {id: fact-pledges, board: s, half: small, kind: fact, label: "...", seen_from: p-organiser, cites: [s4]}
217
+ sources: [s4]
218
+ set:
219
+ - {id: place-mills, label: "...", label_note: "regional mills"}
220
+ remove:
221
+ - {id: gap-risk, reason: "answered by the pledges (H1, s4)"}
222
+ state:
223
+ accepted: ["pledged members carry unsold dough"]
224
+ ```
225
+
226
+ Top level: `id`, `case`, `applies_to` (the board's id), `version` (the board's new `state.version`, kept as text; an unquoted `5.8` is read as `"5.8"`, but quote it, since YAML reads `5.10` as `5.1`), `date`, `cites`, `notes` (the patch's own note), `title` (replaces the board's title), `board_notes` (see below), and:
227
+ - `add`: items appended to `elements`, `relations`, `transitions`, `qualifications`, `theses`, `hypotheses`, `ontology`, `signoffs`, in the board's shapes; `sources`: manifest ids added to the board's `sources`. The added items are checked by the board schema after application, not in the patch.
228
+ - `set`: `{id, <field>: value}` merged into the element, K, thesis, hypothesis or ontology record of that id; `null` deletes the field. `{n, <field>: value}` does the same for the transition numbered `n` (`n` itself is not changed). When both the old and the new value are mappings (`detail`, `move`, `tags`, `aspects`), they merge one level deep, key by key, and `null` deletes that key; lists and scalars replace. So `{id: x, detail: {keys: {tier: tier}}}` keeps the table's `columns` and `rows`, and `{id: m, move: {from: [a, b]}}` keeps the move's `op`.
229
+ - `board_notes`: text appended to the board's `notes`, before the lines for removals.
230
+ - `remove`: `{id, reason}` or `{n, reason}`. `n` drops that transition (later ones renumbered 1..n) and notes `<patch id>: removed transition <n>: <reason>`. The item is dropped with every link that names it; each id and reason is appended to the board's `notes` as `<patch id>: removed <id>: <reason>`, followed by `; dropped sign-off <function> <id> (<by>, <date>)` for each sign-off dropped with it. Links dropped: relations and transitions from or to it (the remaining transitions are renumbered 1..n), `reflected_from`, element `seen_from`, `performed_by` and `in`, qualification `targets`, thesis `feeds`, `about` and `raises`, hypothesis `targets` and `qualifications`, ontology `valid_for`, `subjects` of that position, `cooperation` and `test`, sign-offs on it, and its entry in `state.open`. A list left empty is deleted, except `valid_for` and a move's `from`, which stay empty so that the validator reports them.
231
+ - `state`: `accepted` and `open` entries are appended, `remove_open` entries dropped from `open`, `next_move` and `difficulty` replaced.
232
+
233
+ `smd apply <board> <patch> -o <out> [--sources <manifest>] [--taxonomies <file>]` applies `add`, `set`, `board_notes`, `remove`, `sources`, `state` and `title` in that order, sets `state.version`, appends the patch id to `applied`, and validates the result, with the manifest and the taxonomy file when given; an error in an item names the item's id. A patch for another board, a `set` or `remove` naming an unknown id or transition number, or, with `--sources`, a patch `cites` entry that is not in the manifest exits 2. A transition `n` in `set` and `remove` is its number after `add`, before this patch renumbers; so `add` may give a transition and `set` may change it. The notes get `board_notes` (trailing whitespace dropped), then one line per removal, each joined to the notes by one newline. A patch already in `applied` exits 1: a patch applies once. A result with errors is not written and the command exits 1; `--force` writes it and still exits 1. The output is in the engine's YAML layout (`dump_record`), so a board written by the engine changes only in the patched lines; comments of a hand-written board are not kept. `smd revise` does not carry `applied` into the next cycle.
234
+
235
+ ## Profiles
236
+
237
+ A profile is a YAML file in `profiles/` of the install: the repository folder in a checkout, `smd_engine/assets/profiles` in the wheel. A record names its profile by id in `profile`; there is no per-case profile path, so a new profile is added to `profiles/` of the install. The schema leaves `board`, `kind` and the relation `kind` as strings; the validator checks them against the record's profile, and an unknown board or kind is an error naming the element and the value. Two profiles ship:
238
+ - `program-three-boards` (default): the program's screen (`screen: triangles`), boards `od`, `oo` and `s` (halves `small`, `big`), the 19 kinds above, every hint;
239
+ - `classic-two-boards`: boards `od` and `oo` side by side (`screen: regions`); no `entry` and no situation kinds, plus `reflexive_position` and `norm`; no `entry` or situation hints; no SessionFlow export. Example: `cases/example/two-boards-c1.board.yaml`.
240
+
241
+ The file format, the layout of boards by their regions and the checks of a profile file are in `docs/profiles.md`. Two sets of ids are fixed by the engine:
242
+ - **Kinds every profile defines:** `position`, `place_for_others`, `function`, `cooperation`, `gap`, `note`; also `entry` when the profile enables the hint `entry`. The checks, the frames switches and the fold counter name these ids; other meanings come from kind attributes (`shape: frame`, `light_off`) and the board flag `object`. No board id is fixed.
243
+ - **Reading step ids:** `entry`, `positions`, `places`, `small`, `big`, `old-frame`, `new-frame`, `object`, each at most once: the steps `smd frames` has titles and texts for.
244
+
245
+ `smd pack` reads `profile` in a procedure's front matter (`any` or a profile id). A procedure for another profile than the records' is refused (exit 2, "procedure is for profile ..."); a procedure it names is left out. `smd compare`, `smd frames --by cycle` and the cycle frames of `smd publish` take records of one profile; records of two profiles exit 2 with a message naming both.
246
+
247
+ ## Migration
248
+
249
+ `smd migrate <paths> [--write]` rewrites board sets and manifests (files or folders) to the current schema, one step per version (`src/smd_engine/migrate.py`). It prints what each file changes; without `--write` it only reports. A current record, of any type, is left as it is; an unquoted version (`schema_version: 0.2`) is read as text. The changes are made in the file's text, line by line, so comments and layout stay; the edited text is loaded back and compared with the migrated data. When they differ, the file is rewritten in the engine's YAML layout (JSON for a `.json` file) and the report says so ("rewritten, about N comment line(s) not kept"; the count is approximate).
250
+
251
+ From 0.2 to 0.3: `schema_version` becomes `"0.3"`; an element `voice` outside the enum is set to its lowercase form when that is in the enum (`AI` to `ai`; `ИИ` also becomes `ai`), otherwise to `participant`, with the old text kept in `voice_note`. Nothing else in 0.3 is stricter than 0.2. The profile checks came later within 0.3 and need no migration: the default profile holds exactly the boards, kinds and relation kinds of the 0.3 enums.
252
+
253
+ ## Checks
254
+
255
+ The checks are in `src/smd_engine/validate/`, one module per subject, run in the order of its `REGISTRY`; hints run only when the record's profile enables them. An unknown profile, or a board, kind or relation kind outside the profile, is an error reported with the schema errors, and like them it stops the checks:
256
+ - **Errors:** duplicate ids; `fillers` on an element that is not a role; a `detail` row whose length differs from `columns`; dangling references in `seen_from`, `in`, relations, transitions, qualification targets, move sources and thesis `feeds`; `seen_from` or `reflected_from` that is not a position; `half` rules; containment cycles; transition numbering; a closed K without `closed_in`; a K raised after the record's cycle; a record revising itself; `verified` without a checker; unknown source ids when a manifest is given.
257
+ - **Errors (ingestion):** `of` on an element that is not a gap; `performed_by` on a non-function or not naming a position or place for others; a thesis that `raises` an unknown K; a hypothesis result without its text, field artefact or `resolved_in`; dangling hypothesis targets or K; a thesis status that does not fit its voice; an accepted move from an AI voice with no `accept_move` sign-off; a sign-off by an AI voice, without a target, or aimed at the wrong kind of target.
258
+ - **Warnings:** a screen label (`short`, or `label` when there is no `short`) over 60 characters, which the screen cuts; a kind off its usual board; a content item without a position; a transition inside one board; a `shift` between non-positions; a manifest entry with no file or url, or no clean-room decision.
259
+ - **Warnings (ingestion):** a revising cycle with no `reflected_from`; a thesis with no source address (except AI voice); an accepted move with neither a source nor a sign-off; a closed K, a `refined` thesis, a hypothesis result or a `verified` record with no matching sign-off; a hypothesis `testing` with no action; a sign-off that changes nothing, because its target is not in the state it licenses (only the latest K or move sign-off on a target is checked: `reopen_k` supersedes an earlier `close_k`).
260
+ - **Hints** (Girenko's criteria for a group screen, 17.10.2026): no entry; an empty board, or an empty half of a halved board (the program's small or big situation not laid out); one position and no places for others; a new frame without functions or with functions no one performs; open gaps; open qualifications; candidate moves, on elements and theses; AI variants awaiting the group.
261
+ - **Warnings (step 3):** subject-object talk ("we must", "our task", "мы должны", "наша задача") in a label on a board the profile flags `object` (the object board); `represents` not starting from a position; an ontology subject for a position outside `valid_for`.
262
+ - **Errors (step 3):** ontology `valid_for` that is not a position, `cooperation` that is not a cooperation element, `test` that is not a hypothesis.
263
+ - **Hints (classic method):** an ontology with no realizability test, or valid for several positions with no cooperation; a gap or a problem thesis with no scheme block; a new cycle with no `difficulty`; a hypothesis with no `revise_if`; degenerate belts (GP1987): mostly М-К theses and none in М ("play with words"), or М theses with none in мД (thinking cut off from the situation).
264
+ - **Errors (v0.3):** `scheme` on an element that is not a gap or on a thesis that is not a problem; a `detail.keys` column that is not among `columns`; with a taxonomy file, an unknown taxonomy or value in `tags` or in a bound column.
265
+ - **Warnings (v0.3):** `tags` or `detail.keys` used and no taxonomy file given.
266
+ - **Across cycles:** when the revised record is among the files validated, a new cycle with no new or changed frame, function, model, concept, standard or cooperation gets the hint "reflection without a new scheme".
@@ -0,0 +1,90 @@
1
+ # Lenses
2
+
3
+ A lens is a content model that a procedure offers at a given step. It says when to use it, which questions it asks, and which elements of a board-set record the answers fill. A lens adds no procedure of its own: the procedure runs, the lens is a set of questions it may put to the human, and the human decides whether to use it.
4
+
5
+ One Markdown file per lens, `lenses/<id>.md`. The checks are in `tests/test_lenses.py`.
6
+
7
+ ## Front matter
8
+
9
+ | Key | Meaning |
10
+ |---|---|
11
+ | `id` | the file name without `.md`, lowercase-hyphen; the value of `lens:` on elements and theses |
12
+ | `title` | one line |
13
+ | `version` | semver; bump on any change of the questions or the output slots |
14
+ | `source` | who and where: lecture or slides, or the primary-text keys of `docs/method/sources.md` |
15
+ | `route` | `smd`, `triz` or `both`: the way of thinking the lens belongs to (`docs/method/reconciliation.md` section 8) |
16
+ | `offered_by` | procedure ids that offer the lens; must agree with the selection table below |
17
+
18
+ Sections of every lens, in this order or close to it:
19
+ - **When to use**, with the procedure sections where it is offered, and when not to use it;
20
+ - **Questions**, numbered, each answerable from a session;
21
+ - **Output slots**, a table: answer, then element kind, board and field. Only kinds and fields of `docs/data-model.md` are used. Every element and thesis carries `lens: <id>`;
22
+ - **Limits**: what the source does not say, and which claims are unverified.
23
+
24
+ Each lens marks which statements are the source's and which are the engine's own reading. The mark sets differ by lens (`[V]`, `[S]`, `[X]`, `[ANALYSIS]`, `[UNVERIFIED]`, "own extrapolation"); each file defines its marks near the top.
25
+
26
+ ## How a procedure applies a lens
27
+
28
+ 1. The procedure reaches the step in the table and offers the lens in one line, naming the questions. The human may decline.
29
+ 2. The lens questions are asked in the procedure's role. Wording that the model writes is `voice: ai`, with `epistemic: hypothesis` unless a registered source supports it, as in `roles/README.md`.
30
+ 3. The answers go to the slots of the lens and are tagged `lens: <id>`. `smd compare` reads the tag (`compare.py`).
31
+ 4. Where a procedure writes no elements (`qualification-pass` writes only K) or restricts them (`small-situation` writes only the small half), the table says "questions only" or names the one allowed output. The lens then supplies the check behind a K, a label or a thesis, and the text names the lens id.
32
+ 5. A lens never writes a sign-off, a status or a result. Moves it proposes are `candidate`.
33
+
34
+ Elements, theses, hypotheses and ontology records carry `lens: <id>`; `smd compare` lists the lenses each board set used. Qualifications and relations have no `lens` field.
35
+
36
+ ## Lenses
37
+
38
+ | Lens | Route | Source | What it asks |
39
+ |---|---|---|---|
40
+ | `khaprov-levels` | smd | Khaprov, lecture | at which of seven levels of management complexity a problem is decided; the three kinds of complexity |
41
+ | `khaprov-ontology` | smd | Khaprov, lecture; GP1981 | three scales; the ontology test; managing not-knowing |
42
+ | `schukin-boundaries` | smd | Schukin, written talk | five grounds of a cut between functions; four families of change; capability probes |
43
+ | `schukin-interface-tax` | smd | Schukin, written talk | the function of each human touchpoint; achievement and balancing metrics; the engine's own sign-offs |
44
+ | `fliamer-diamond` | smd | Fliamer, module-1 slides | complexes of the big situation: functions, products, consumption, new technical solutions |
45
+ | `design-chain` | smd | team design study (local) | goal, beneficiary, subject, form, instruments, conditions, result of a pass between positions |
46
+ | `triz-contradictions` | triz | ARIZ-85V, Altshuller | a contradiction inside one object: zone, time, resources, ideal result, separation |
47
+ | `scamper-check` | smd | Eberle, Osborn; Zharnikov | a cross-check of the four structural moves after the tract |
48
+
49
+ ## Selection table
50
+
51
+ Rows are procedure and step. "Q" is the question number in the lens file. Where a procedure or step is missing from the table, no lens is offered there.
52
+
53
+ | Procedure | Step | Lens | Questions | Why here |
54
+ |---|---|---|---|---|
55
+ | `small-situation` | 2 Where we are | `khaprov-ontology` | Q1 | The procedure allows the scale question when the human is unsure; the answer goes into the fragment label, no new element |
56
+ | `small-situation` | 7 Record, sign-offs | `schukin-interface-tax` | Q11 | Asks no sign-off that changes nothing |
57
+ | `visionary-tract` | 1 Boundary of normality | `fliamer-diamond` | Q1, Q2 | Names the system of activity as fields and complexes |
58
+ | `visionary-tract` | 4 Bridge (respecify) | `fliamer-diamond` | Q6 | The change channels (new functions, cheaper delivery, customised products, demanded norms) say which links change |
59
+ | `visionary-tract` | 5 View of oneself (rescale) | `khaprov-ontology` | Q1-Q4 | The three scales are the grain options of the move |
60
+ | `visionary-tract` | after 6, before the hand-over | `scamper-check` | all | Looks for candidates the four-move pass missed |
61
+ | `visionary-tract` | 10 Hand over | `schukin-interface-tax` | Q11 | As above |
62
+ | `positional-construction` | 2 Positional set | `khaprov-levels` | Q1-Q3, Q10 | Asks whether a position acts at the level its task needs, and whether the missing one is a gap or missing knowledge |
63
+ | `positional-construction` | 3 The construction | `design-chain` | Q1-Q5, Q9 | The `flow` relations label what passes, from whom, to whom; the chain asks what stays behind |
64
+ | `positional-construction` | 5 One common object | `khaprov-ontology` | Q5-Q11 | The ontology record's `subjects`, `valid_for`, `competes`, `cooperation` and `test` |
65
+ | `functional-plan` | 2 Draft the chain, 5 Align | `fliamer-diamond` | Q3-Q5 | Functions in a chain, products, consumers; material on how the chain is arranged elsewhere |
66
+ | `functional-plan` | 3 Performs, pays, receives | `design-chain` | Q6, Q9 | Instruments with performer and payer; empty links of the chain |
67
+ | `functional-plan` | 4 Empty places | `khaprov-levels` | Q2, Q3 | A function with no performer: is the performer missing at a higher or lower level, or is knowledge missing |
68
+ | `functional-plan` | 6 Candidate moves | `schukin-boundaries` | Q1-Q8, Q14 optional | The four families and five grounds the procedure already names; grounds for `respecify` |
69
+ | `functional-plan` | 6, after the line above | `schukin-interface-tax` | Q1-Q10 | The human touchpoints that remain after a merge or shared state |
70
+ | `functional-plan` | 6 | `triz-contradictions` | Q0 first | Only when one function carries two opposed demands; Q0 sends a paradox between positions back to the SMD route |
71
+ | `functional-plan` | 7 Hypotheses | `schukin-boundaries` | Q9-Q13 | Capability probes are ready-made hypotheses `assumes`, `action`, `observe`, `revise_if` |
72
+ | `functional-plan` | 9 Hand over | `schukin-interface-tax` | Q11 | As above |
73
+ | `problematization` | 2 Sort what is on the table | `khaprov-ontology` | Q12-Q14 | A difficulty may be a wrongly posed problem; output is a thesis |
74
+ | `problematization` | 6 Pointer to the other route | `triz-contradictions` | all | The procedure flags a contradiction inside the object and writes no TRIZ terms; the lens is a separate pass |
75
+ | `qualification-pass` | 3.1 C7, 3.3 realizability | `khaprov-ontology` | Q1-Q4, Q9-Q11 | Questions only: the scale and the stated reason; the test and the cooperation. No elements |
76
+ | `qualification-pass` | 6 Hand over | `schukin-interface-tax` | Q11 | As above |
77
+ | `reflexive-protocol` | 6 Sign-offs | `schukin-interface-tax` | Q11 | As above |
78
+ | `theses-to-board` | 5 Sign-offs | `schukin-interface-tax` | Q11 | As above |
79
+ | `solo-session` | none of its own | all of the above | by step | It orders the step procedures and inherits their lenses; the human may call another lens at a gate |
80
+ | `ingest-transcript`, `field-result` | none | none | | They turn a record of talk or a field artefact into theses or a result, and a lens would put the model's questions into that reading |
81
+
82
+ The table is the engine's selection. The source materials do not say which lens belongs at which step. A procedure already names the source in these places: the scale test at tract step 5 and in `qualification-pass`, the four families and five grounds at functional-plan section 6, the ontology test at positional-construction section 5, the sign-off rule everywhere.
83
+
84
+ ## Rules for every lens
85
+
86
+ - Generic: no participants' names and no case facts. Examples use the invented bakeries case in `cases/example/`.
87
+ - Source material is kept outside the repository and never committed. A lens names the source and the section, and marks what it could not check.
88
+ - Plain English; Russian only for the source's terms, given once in brackets.
89
+ - Under 200 lines per file.
90
+ - A lens that needs a kind or field the format lacks reports it in Limits, and does not invent a field.