surfaceplate 0.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. surfaceplate/MANIFEST.sha256 +230 -0
  2. surfaceplate/VERSION +1 -0
  3. surfaceplate/__init__.py +18 -0
  4. surfaceplate/about.py +48 -0
  5. surfaceplate/adapters/python.md +5 -0
  6. surfaceplate/adapters/r.md +3 -0
  7. surfaceplate/adapters/typescript.md +5 -0
  8. surfaceplate/adopt/__init__.py +15 -0
  9. surfaceplate/adopt/catalogue.py +78 -0
  10. surfaceplate/adopt/defaults.py +278 -0
  11. surfaceplate/adopt/detect.py +141 -0
  12. surfaceplate/adopt/discover.py +614 -0
  13. surfaceplate/adopt/example_answers.py +128 -0
  14. surfaceplate/adopt/explanations.py +551 -0
  15. surfaceplate/adopt/flow.py +559 -0
  16. surfaceplate/adopt/interview.py +237 -0
  17. surfaceplate/adopt/plan.py +1330 -0
  18. surfaceplate/adopt/provenance.py +300 -0
  19. surfaceplate/adopt/render.py +282 -0
  20. surfaceplate/adopt/scaffold.py +343 -0
  21. surfaceplate/adopt/sections.py +287 -0
  22. surfaceplate/adopt/tui/__init__.py +7 -0
  23. surfaceplate/adopt/tui/app.py +211 -0
  24. surfaceplate/adopt/tui/app.tcss +294 -0
  25. surfaceplate/adopt/tui/mark.py +77 -0
  26. surfaceplate/adopt/tui/screens.py +1772 -0
  27. surfaceplate/adopt/validators.py +224 -0
  28. surfaceplate/adopt/wizard.py +932 -0
  29. surfaceplate/check_conformance.py +3664 -0
  30. surfaceplate/cli.py +268 -0
  31. surfaceplate/core/AI_OPERATING_MODEL.md +45 -0
  32. surfaceplate/core/CONFORMANCE_LEVELS.md +299 -0
  33. surfaceplate/core/CONTROL_PRINCIPLES.md +14 -0
  34. surfaceplate/core/PREREQUISITE_GATES.md +322 -0
  35. surfaceplate/core/REVIEW_AND_EVIDENCE.md +55 -0
  36. surfaceplate/core/SECURITY_BASELINE.md +29 -0
  37. surfaceplate/doctor.py +249 -0
  38. surfaceplate/examples/application-profile.essential.example.yaml +126 -0
  39. surfaceplate/examples/application-profile.full.example.yaml +315 -0
  40. surfaceplate/examples/method-registry-entry.example.yaml +78 -0
  41. surfaceplate/examples/method-run-lineage.example.yaml +44 -0
  42. surfaceplate/examples/override-record.approved.example.yaml +39 -0
  43. surfaceplate/install_standard.py +880 -0
  44. surfaceplate/rules.py +148 -0
  45. surfaceplate/schemas/README.md +24 -0
  46. surfaceplate/schemas/application-profile.schema.yaml +320 -0
  47. surfaceplate/schemas/assurance-evidence.schema.yaml +33 -0
  48. surfaceplate/schemas/gate-exception.schema.yaml +47 -0
  49. surfaceplate/schemas/method-registry-entry.schema.yaml +132 -0
  50. surfaceplate/schemas/method-run-lineage.schema.yaml +72 -0
  51. surfaceplate/schemas/override-record.schema.yaml +76 -0
  52. surfaceplate/seeds/CHANGELOG.md +18 -0
  53. surfaceplate/seeds/activity-register.md +47 -0
  54. surfaceplate/seeds/adoption-decision-record.md +30 -0
  55. surfaceplate/seeds/data-source-register.md +18 -0
  56. surfaceplate/seeds/decision-log.md +28 -0
  57. surfaceplate/seeds/dependency-review-log.md +18 -0
  58. surfaceplate/seeds/findings-register.md +26 -0
  59. surfaceplate/seeds/method-registry-readme.md +7 -0
  60. surfaceplate/seeds/options-log.md +18 -0
  61. surfaceplate/seeds/output-validation-log.md +18 -0
  62. surfaceplate/seeds/overrides-readme.md +7 -0
  63. surfaceplate/seeds/release-checklist.md +19 -0
  64. surfaceplate/seeds/risk-classification.md +24 -0
  65. surfaceplate/seeds/run-lineage-readme.md +7 -0
  66. surfaceplate/seeds/source-of-truth-matrix.yaml +25 -0
  67. surfaceplate/seeds/test-conventions.md +20 -0
  68. surfaceplate/standard/.githooks/.gitattributes +1 -0
  69. surfaceplate/standard/.githooks/pre-commit +35 -0
  70. surfaceplate/standard/.github/skills/bug-fix/SKILL.md +57 -0
  71. surfaceplate/standard/.github/skills/change/SKILL.md +62 -0
  72. surfaceplate/standard/.github/skills/dependency-update/SKILL.md +61 -0
  73. surfaceplate/standard/.github/skills/fix-ci/SKILL.md +68 -0
  74. surfaceplate/standard/.github/skills/release/SKILL.md +71 -0
  75. surfaceplate/standard/.github/skills/review/SKILL.md +56 -0
  76. surfaceplate/standard/.github/skills/security-review/SKILL.md +61 -0
  77. surfaceplate/standard/.github/workflows/standards-conformance.yml +38 -0
  78. surfaceplate/standard/agent-instructions/activity.md +78 -0
  79. surfaceplate/standard/agent-instructions/ai-workflow.md +109 -0
  80. surfaceplate/standard/agent-instructions/authority.md +72 -0
  81. surfaceplate/standard/agent-instructions/provenance.md +93 -0
  82. surfaceplate/standard/agent-instructions/security.md +78 -0
  83. surfaceplate/standard/agent-instructions/tests.md +71 -0
  84. surfaceplate/standard/conformance-block.md +31 -0
  85. surfaceplate/templates/application-profile.yaml +113 -0
  86. surfaceplate/templates/decision-record.md +42 -0
  87. surfaceplate/templates/gate-exception.yaml +23 -0
  88. surfaceplate/templates/override-record.yaml +25 -0
  89. surfaceplate/templates/work-packet.md +33 -0
  90. surfaceplate-0.16.0.dist-info/METADATA +19 -0
  91. surfaceplate-0.16.0.dist-info/RECORD +96 -0
  92. surfaceplate-0.16.0.dist-info/WHEEL +4 -0
  93. surfaceplate-0.16.0.dist-info/entry_points.txt +2 -0
  94. surfaceplate-0.16.0.dist-info/licenses/LICENSE +201 -0
  95. surfaceplate-0.16.0.dist-info/licenses/LICENSE-DOCS +121 -0
  96. surfaceplate-0.16.0.dist-info/licenses/NOTICE +24 -0
@@ -0,0 +1,551 @@
1
+ """What each item the wizard asks about actually means, in two registers.
2
+
3
+ `DR-35` records why this module exists: a real, live adoption session hit both a data-loss bug
4
+ (`F36`) and a genuine fit gap — three consecutive "I don't know what this is" answers at the start
5
+ of the gate catalogue, and a wizard whose only register was terse and unexplained. The maintainer's
6
+ own correction, preserved because it is the whole design constraint this module exists to satisfy:
7
+ *"keep in mind that we need much more detail and guidance compared to the one we have for advanced
8
+ as well. don't ever think what we have now would be suitable to advance."* Both registers explain;
9
+ they differ in the vocabulary they assume, not in whether an explanation is given at all.
10
+
11
+ - **`simple`** — plain English, zero jargon, practical framing. Assumes no software-development or
12
+ governance background. Says what a check actually verifies and, just as importantly, what it does
13
+ *not* verify, wherever that distinction exists — an unqualified "this is checked" would overclaim
14
+ exactly the way `core/CONFORMANCE_LEVELS.md` itself warns against.
15
+ - **`advanced`** — precise technical/software terminology. Assumes real engineering literacy, but
16
+ not familiarity with *this framework's own* vocabulary — "prerequisite gate" is still explained,
17
+ just in language a competent engineer reads instantly rather than needing translated.
18
+
19
+ Grounded, not invented: every `advanced` entry traces to `core/PREREQUISITE_GATES.md`,
20
+ `core/CONFORMANCE_LEVELS.md`, or `core/CONTROL_PRINCIPLES.md`. Every `simple` entry is new authoring
21
+ — none of these three documents has a plain-English register today, which the maintainer's approval
22
+ of the first worked example (`agent_work_packets`) surfaced as a real documentation gap in its own
23
+ right, not merely a wizard gap.
24
+
25
+ `tests/test_adopt.py` asserts every gate ID in `catalogue.GATE_CATALOGUE` and every control ID in
26
+ `catalogue.CONFORMANCE_LEVELS["full"]` (the union across all levels) has a non-empty entry in both
27
+ registers here — a coverage test that fails loudly on a gap, rather than a wizard that silently
28
+ falls back to no explanation for whatever was missed.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ MODES = ("simple", "advanced")
34
+
35
+ # ---------------------------------------------------------------------------------------------
36
+ # The three baseline controls — required at every level, and named `agent_work_packets`,
37
+ # `actual_diff_review`, `secret_hygiene` in that fixed order throughout this package.
38
+ # ---------------------------------------------------------------------------------------------
39
+
40
+ _BASELINE_CONTROLS: dict[str, dict[str, str]] = {
41
+ "agent_work_packets": {
42
+ "simple": (
43
+ "When an AI coding assistant does work in this repository, it should be given a clear, "
44
+ "written brief first - what to do, what not to touch, and how you'll know it's done - "
45
+ "rather than a vague, open-ended instruction. This control is your repository saying "
46
+ "\"yes, we do that here.\" Nothing checks this automatically; it's a promise you're "
47
+ "making, not something the tool can verify for you."
48
+ ),
49
+ "advanced": (
50
+ "Every AI-assisted task is scoped by a written work packet before implementation begins "
51
+ "- objective, explicit non-goals, bounded ownership area, acceptance criteria, required "
52
+ "verification. Declaring this control asserts that discipline is followed; it's one of "
53
+ "two baseline controls this framework does not verify, because doing so would mean "
54
+ "judging the quality of human work."
55
+ ),
56
+ },
57
+ "actual_diff_review": {
58
+ "simple": (
59
+ "When someone reviews a change before it ships, they should look at what actually "
60
+ "changed - the real lines of code - not just a description of what the change was "
61
+ "supposed to do. This control is your repository saying reviewers read the real diff. "
62
+ "Like agent_work_packets, nothing checks this automatically; it's a discipline you're "
63
+ "committing to, not something the tool can verify."
64
+ ),
65
+ "advanced": (
66
+ "Every material change is reviewed against its actual diff content, not a summary, a "
67
+ "changelog entry, or a description of intent. The other of the two baseline controls "
68
+ "this framework does not verify - verifying it would mean the tool judging the quality "
69
+ "of a human review, which `core/CONTROL_PRINCIPLES.md` principle 9 places outside what "
70
+ "a tool may claim."
71
+ ),
72
+ },
73
+ "secret_hygiene": {
74
+ "simple": (
75
+ "Secrets - passwords, API keys, tokens - should never end up committed into your "
76
+ "repository's history, because once they are, they're effectively public even after "
77
+ "you delete them. This control means you have an automated scanner checking every "
78
+ "change for exactly that, wired in so it can actually stop a change, not just warn "
79
+ "about it afterwards. This is the one baseline control the tool actually checks: it "
80
+ "confirms a scanner is really wired in and can fail the build. It does not scan your "
81
+ "files itself, and a pass here doesn't mean your repository has no secrets in it "
82
+ "already - only that new ones won't slip through unnoticed."
83
+ ),
84
+ "advanced": (
85
+ "A named scanner is wired into a build step capable of failing the job. The one "
86
+ "baseline control this framework verifies rather than merely declares: `SP046` checks "
87
+ "the scanner and its wiring are named; `SP047` confirms the step's exit code can "
88
+ "actually fail the job, not merely report. A pass says a scanner is wired somewhere "
89
+ "that can fail; it says nothing about whether secrets are currently present - this "
90
+ "standard ships no scanner and inspects no file contents itself."
91
+ ),
92
+ },
93
+ }
94
+
95
+ # ---------------------------------------------------------------------------------------------
96
+ # The nine conformance-level controls (the union across essential/standard/full). Source:
97
+ # core/CONFORMANCE_LEVELS.md, read directly for the "reason" column and the "currently checked"
98
+ # table, never restated from memory.
99
+ # ---------------------------------------------------------------------------------------------
100
+
101
+ _LEVEL_CONTROLS: dict[str, dict[str, str]] = {
102
+ "dependency_lock": {
103
+ "simple": (
104
+ "Every project depends on other people's code - libraries, packages. A dependency lock "
105
+ "file pins the exact versions you're actually using, so \"it worked yesterday\" can't "
106
+ "quietly become \"a dependency changed underneath us and now something's different.\" "
107
+ "This one is actually checked: the tool confirms the lock file you name is real, has "
108
+ "content, and is tracked in git - not just that you said you have one."
109
+ ),
110
+ "advanced": (
111
+ "Every dependency's exact version is pinned in a lock file, named as this control's "
112
+ "`implementation_reference`. Verified - `SP051` confirms the file exists, is non-empty, "
113
+ "carries no template placeholder, and is tracked by git. It does not confirm the "
114
+ "versions pinned are the ones actually installed, or that they are good ones."
115
+ ),
116
+ },
117
+ "deterministic_tests": {
118
+ "simple": (
119
+ "A test suite that gives a different answer each time it runs isn't really testing "
120
+ "anything - you can't tell if a red result means a real bug or just bad luck. This "
121
+ "control names a real CI step that runs your tests and can actually fail the build if "
122
+ "they fail. It's checked: the tool confirms that step exists, runs something, and its "
123
+ "failure is binding - but it can't tell whether your tests are good ones, or even "
124
+ "whether they assert anything at all."
125
+ ),
126
+ "advanced": (
127
+ "A named CI step runs the test suite, and its failure is binding on the build. Verified "
128
+ "via `SP053` - the checker confirms the step exists, runs something, and can fail the "
129
+ "job. It does not confirm the tests are deterministic, that they assert anything, or "
130
+ "that they test the right thing - a step running `true` would technically pass."
131
+ ),
132
+ },
133
+ "contract_tests": {
134
+ "simple": (
135
+ "If another team, another service, or a separate frontend relies on the shape of what "
136
+ "your code returns, a contract test catches you breaking that shape before it ships - "
137
+ "instead of the other side finding out at runtime. Checked the same way as "
138
+ "deterministic_tests: a real, failure-binding CI step, confirmed to exist and actually "
139
+ "able to fail."
140
+ ),
141
+ "advanced": (
142
+ "A named CI step runs contract tests against the interface's declared shape, with "
143
+ "binding failure. Verified via `SP053`, identically to `deterministic_tests` - same "
144
+ "mechanism, different step name, matched by name rather than by job."
145
+ ),
146
+ },
147
+ "documentation_authority": {
148
+ "simple": (
149
+ "When two documents disagree about how something works, and nobody's said which one "
150
+ "wins, people end up trusting whichever one they happened to open. This control means "
151
+ "you have a single, real map of which document is the final word for which part of "
152
+ "your project. It's checked indirectly: declaring this control requires the "
153
+ "authority_map gate to also be required, and that gate's own artefact is what gets "
154
+ "verified."
155
+ ),
156
+ "advanced": (
157
+ "A machine-readable map states which document governs which path, closing the "
158
+ "ambiguity contradictory authority creates. Checked through the `authority_map` gate "
159
+ "rather than directly: `SP052` requires that gate whenever this control is declared "
160
+ "required, closing the seam a level being a floor rather than a ceiling would otherwise "
161
+ "leave open."
162
+ ),
163
+ },
164
+ "provenance": {
165
+ "simple": (
166
+ "If a number your system produces really matters, you should be able to trace it back "
167
+ "to exactly what data and settings went into it - not just trust that it's probably "
168
+ "right. provenance and run_lineage share the same kind of record; provenance is the "
169
+ "half that's about tracing back to inputs. The tool checks that every record you file "
170
+ "is well-formed and points at something real - it can't tell you the record is true, "
171
+ "only that it's complete and structurally sound."
172
+ ),
173
+ "advanced": (
174
+ "A material result traces to its inputs - recorded in the same record type "
175
+ "`run_lineage` uses, distinguished by which cross-reference each control obliges: "
176
+ "`provenance` requires the run to resolve to every override that adjusted the result. "
177
+ "Verified via `SP055`/`SP056` (schema-valid records in a declared directory) and "
178
+ "`SP057` (the reference itself resolves) - never that the record is true."
179
+ ),
180
+ },
181
+ "run_lineage": {
182
+ "simple": (
183
+ "Being able to trace inputs isn't the same as being able to re-run the exact thing "
184
+ "that produced a result. run_lineage is the half of the record that's about "
185
+ "reproducibility - this run, with this method, at this version, produced this output. "
186
+ "Checked the same structural way as provenance: the record must exist, be well-formed, "
187
+ "and its reference to the method that ran it must resolve to something real in your "
188
+ "registry."
189
+ ),
190
+ "advanced": (
191
+ "A material result is reproducible from a recorded execution - the run resolves to the "
192
+ "method (and method version) that produced it. Same record type as `provenance`; "
193
+ "distinguished by requiring the method reference specifically, not the override "
194
+ "reference. Verified via `SP055`/`SP056`/`SP057`."
195
+ ),
196
+ },
197
+ "method_registry": {
198
+ "simple": (
199
+ "If a calculation method changes over time, someone needs a real place recording which "
200
+ "version is current, whether it's been validated, and who approved it - otherwise "
201
+ "\"which version did we actually use\" becomes a guess. This is a register: a directory "
202
+ "of records, checked to be well-formed. An empty register is a genuinely honest answer "
203
+ "if you haven't governed any methods yet - it's not treated as a failure."
204
+ ),
205
+ "advanced": (
206
+ "Governed methods carry identity, lifecycle, validation, and approval state, in a "
207
+ "directory of records validating against `method-registry-entry.schema.yaml`. Verified "
208
+ "via `SP055`/`SP056`. An empty register passes deliberately - the check establishes no "
209
+ "unvalidated record exists, never that any records exist."
210
+ ),
211
+ },
212
+ "overrides": {
213
+ "simple": (
214
+ "Sometimes a human has to manually adjust a number a system produced - that's fine, but "
215
+ "it should never happen invisibly, buried in a spreadsheet formula or a line of code "
216
+ "nobody notices. This control means every manual adjustment gets its own real, visible "
217
+ "record: what changed, why, who approved it. Checked structurally the same way as the "
218
+ "other record-based controls."
219
+ ),
220
+ "advanced": (
221
+ "A manual adjustment is never hidden in UI or calculation code - recorded instead, "
222
+ "carrying classification, before/after values, rationale, evidence, owner, impact, "
223
+ "approval, and rollback approach (`core/CONTROL_PRINCIPLES.md` principle 6). Verified "
224
+ "via `SP055`/`SP056`/`SP057` - each override's `method_run_id` must resolve to a real "
225
+ "run in the register it names."
226
+ ),
227
+ },
228
+ "assurance_findings": {
229
+ "simple": (
230
+ "Every real system has limitations - edge cases it doesn't handle well, assumptions "
231
+ "that might not hold. This control is a commitment to writing those down honestly "
232
+ "instead of quietly hoping nobody asks. Checked the same way as dependency_lock: the "
233
+ "file you name must really exist, have real content, and not be a template you forgot "
234
+ "to fill in."
235
+ ),
236
+ "advanced": (
237
+ "Limitations and findings are recorded rather than smoothed away, in a named artefact. "
238
+ "Verified via `SP051`, the same pattern as `dependency_lock` - existence, "
239
+ "non-emptiness, absence of a template placeholder, and that it's tracked by git."
240
+ ),
241
+ },
242
+ }
243
+
244
+ # ---------------------------------------------------------------------------------------------
245
+ # The nineteen prerequisite gates, in core/PREREQUISITE_GATES.md's own catalogue order.
246
+ # ---------------------------------------------------------------------------------------------
247
+
248
+ _GATES: dict[str, dict[str, str]] = {
249
+ # --- Design and user interface ---
250
+ "component_library": {
251
+ "simple": (
252
+ "If your app has a screen with a button, a form, a table - build that piece once, as a "
253
+ "reusable thing, before any screen uses it. This gate checks the order: the reusable "
254
+ "piece has to exist first, then a screen can use it."
255
+ ),
256
+ "advanced": (
257
+ "No screen may consume a UI component that does not already exist in the shared "
258
+ "component library at the time it is introduced - checked against git history, not "
259
+ "just current file state."
260
+ ),
261
+ },
262
+ "design_authority": {
263
+ "simple": (
264
+ "Before you build any screen, decide up front how screens should look and be put "
265
+ "together - a written design policy, plus a set of page templates to build from. This "
266
+ "gate checks that those exist before UI code gets written, not that they get invented "
267
+ "screen-by-screen as you go."
268
+ ),
269
+ "advanced": (
270
+ "A design policy and a set of page templates must exist in the working tree before any "
271
+ "UI code is written - checked against git history, so a policy written after the first "
272
+ "screen still fails the audit even if it exists today."
273
+ ),
274
+ },
275
+ "options_before_build": {
276
+ "simple": (
277
+ "Before building a screen or feature that has real design choices behind it, write "
278
+ "down the alternatives you considered and which one you picked, and why. This gate "
279
+ "isn't about the specific decision - it's about making sure a real choice happened, on "
280
+ "the record, instead of just building the first idea."
281
+ ),
282
+ "advanced": (
283
+ "Design alternatives for a surface are documented, and one is selected, before that "
284
+ "surface is built - checked against history, the same \"decide before, not during\" "
285
+ "shape every gate in this catalogue shares."
286
+ ),
287
+ },
288
+ "prerequisite_state_ui": {
289
+ "simple": (
290
+ "If a screen depends on some data being ready first - an account being set up, a file "
291
+ "being uploaded - it shouldn't show its main workflow as if that data already exists. "
292
+ "This gate is about the screen honestly reflecting the state things are actually in, "
293
+ "rather than presenting a workflow that will fail once someone tries to use it."
294
+ ),
295
+ "advanced": (
296
+ "A screen does not present its main workflow before its stated data prerequisite is "
297
+ "actually satisfied - a UI-specific instance of the general \"X must exist before Y may "
298
+ "begin\" shape, applied to what a screen shows a user rather than to what code may "
299
+ "change."
300
+ ),
301
+ },
302
+ # --- Work and decisions ---
303
+ "work_registration": {
304
+ "simple": (
305
+ "Before any real work starts - writing code, making a change - it should be written "
306
+ "down somewhere as a named, identified piece of work: what it is, who owns it, what "
307
+ "\"done\" looks like. This is the one gate that applies no matter how light your "
308
+ "conformance level is; every repository using this framework registers its work before "
309
+ "starting it."
310
+ ),
311
+ "advanced": (
312
+ "No implementation work begins until it is registered as an identified activity, with "
313
+ "an owner, a scope, and a definition of done - the only gate required at every "
314
+ "conformance level, including `essential`."
315
+ ),
316
+ },
317
+ "work_contract": {
318
+ "simple": (
319
+ "When an AI assistant is going to do implementation work, it should get a real written "
320
+ "brief first - not just a registered activity in general, but the specific scope, "
321
+ "constraints, and what \"done\" means for this particular piece of AI-assisted work. "
322
+ "This gate checks that the brief exists before the AI starts, not that one gets written "
323
+ "afterwards to match what happened."
324
+ ),
325
+ "advanced": (
326
+ "A written work contract - scope, constraints, acceptance criteria - exists before "
327
+ "AI-assisted implementation begins. The gate form of what `agent_work_packets` declares "
328
+ "as a baseline control: this one is checked against the artefact and against history; "
329
+ "that one is not checked at all."
330
+ ),
331
+ },
332
+ "risk_classification": {
333
+ "simple": (
334
+ "Before you start building a change, decide how risky it is - not after you've already "
335
+ "built it and are looking for a reason it was fine. The framework calls this out "
336
+ "directly as the gate most often satisfied on paper while being violated in spirit, "
337
+ "because classifying afterwards just rationalises work that already happened."
338
+ ),
339
+ "advanced": (
340
+ "A change's risk class is decided before implementation begins, not derived afterwards "
341
+ "to match what was built. Named in the framework's own text as the gate most often "
342
+ "violated in spirit while satisfied on paper - classifying after the fact rationalises "
343
+ "work already done rather than genuinely assessing it."
344
+ ),
345
+ },
346
+ "decision_before_implementation": {
347
+ "simple": (
348
+ "For a material change - one that actually matters - write down the decision (what "
349
+ "you're doing and why) before you start building it, not as an afterthought once it's "
350
+ "already shipped. This is one of the gates that becomes mandatory once you're above the "
351
+ "lightest conformance level, because \"we discussed it, trust us\" isn't something "
352
+ "anyone can check later."
353
+ ),
354
+ "advanced": (
355
+ "A decision record exists before implementation of a material change begins - required "
356
+ "at `standard` and above. The evidence for this gate is the order of events in git "
357
+ "history, not merely the record's existence, since a record can always be backdated but "
358
+ "the commit graph cannot."
359
+ ),
360
+ },
361
+ "register_currency": {
362
+ "simple": (
363
+ "If your project keeps a register of work (what work_registration points at), it needs "
364
+ "to actually be up to date at the point you hand work off - not a stale list from three "
365
+ "weeks ago. Any dashboards or views generated from that register have to be regenerated "
366
+ "too, not left showing old data."
367
+ ),
368
+ "advanced": (
369
+ "The work register, and any views generated from it, are current at the point of "
370
+ "handover - checked against history, so a register updated after the handover it was "
371
+ "meant to inform still fails."
372
+ ),
373
+ },
374
+ # --- Documentation authority ---
375
+ "authority_map": {
376
+ "simple": (
377
+ "Have one real, structured file that says which document is the final word for which "
378
+ "part of your project - not scattered opinions in READMEs that might contradict each "
379
+ "other. Without this map, \"the documentation\" is just a phrase, not something anyone "
380
+ "can actually point at and check."
381
+ ),
382
+ "advanced": (
383
+ "A machine-readable map of which document governs which path exists before it can be "
384
+ "relied on elsewhere - the artefact the `documentation_authority` control's own check "
385
+ "reads (`SP052` requires this gate whenever that control is declared)."
386
+ ),
387
+ },
388
+ "authority_same_change": {
389
+ "simple": (
390
+ "When you change something the authority map covers, update the document that governs "
391
+ "it in that same change - not in a follow-up commit that, in practice, often never "
392
+ "arrives. This gate checks that the update actually happened together with the code "
393
+ "change, using git history."
394
+ ),
395
+ "advanced": (
396
+ "A change to a governed path updates its controlling document in the same change - "
397
+ "checked against history, so a documentation update landing in a later, separate commit "
398
+ "still fails the commit that didn't include it."
399
+ ),
400
+ },
401
+ # --- Tests and evidence ---
402
+ "test_convention": {
403
+ "simple": (
404
+ "Write down your own convention for how tests are named and where they live, and then "
405
+ "actually follow it - so a new contributor (human or AI) can find and add tests "
406
+ "predictably instead of guessing. This gate doesn't dictate what your convention is; it "
407
+ "just requires that you have one and stick to it."
408
+ ),
409
+ "advanced": (
410
+ "New tests follow the repository's own declared naming and location standard - the "
411
+ "standard itself is repository-defined; this gate only requires that a declared "
412
+ "convention exists and that new tests are checked against it."
413
+ ),
414
+ },
415
+ "regression_before_merge": {
416
+ "simple": (
417
+ "If a piece of logic really matters, name the test suite that proves it still works, "
418
+ "and don't let a change to that logic merge unless that suite actually passes. This is "
419
+ "about making sure the safety net that exists actually gets used at the moment it "
420
+ "matters, not just sitting there unused."
421
+ ),
422
+ "advanced": (
423
+ "Named regression suites must pass before a change to critical logic is allowed to "
424
+ "merge - the gate ties a specific, named suite to specific critical paths, rather than "
425
+ "relying on \"the tests\" as an undifferentiated whole."
426
+ ),
427
+ },
428
+ "equivalence_evidence": {
429
+ "simple": (
430
+ "If you're refactoring or optimising code without meaning to change what it produces, "
431
+ "don't just eyeball the output and say \"looks the same\" - actually ship evidence (a "
432
+ "real comparison, a diff of results) that proves it. The framework is blunt about this: "
433
+ "\"it looked the same\" is the most common thing offered as proof of a safe refactor, "
434
+ "and it isn't evidence."
435
+ ),
436
+ "advanced": (
437
+ "A performance or refactoring change on a critical path ships evidence that results are "
438
+ "unchanged - a genuine comparison output, not an assertion. The framework's own stated "
439
+ "reason: \"it looked the same\" is the most commonly offered non-evidence for exactly "
440
+ "this claim."
441
+ ),
442
+ },
443
+ # --- Data ---
444
+ "data_source_lifecycle": {
445
+ "simple": (
446
+ "Before a data source can actually be used in your system, it should go through a real "
447
+ "validation and approval step - not just get added to a config file and quietly become "
448
+ "available. The framework's own reasoning: a data source that's merely present, "
449
+ "unapproved, will eventually get used by someone who assumed it was already vetted."
450
+ ),
451
+ "advanced": (
452
+ "A data source completes its validation and approval lifecycle before it becomes "
453
+ "selectable in the system - closing the gap where an unapproved but present source "
454
+ "eventually gets used regardless."
455
+ ),
456
+ },
457
+ "output_validation_before_external_use": {
458
+ "simple": (
459
+ "Before an output your system generates leaves your own team - goes to a client, a "
460
+ "colleague in another team, a downstream system - someone should validate and review it "
461
+ "first. This gate is about the boundary: what happens before it crosses that line, not "
462
+ "after."
463
+ ),
464
+ "advanced": (
465
+ "Generated outputs are validated and reviewed before any use outside the delivery team "
466
+ "- the boundary the gate is scoped to is external use, not generation itself."
467
+ ),
468
+ },
469
+ # --- Dependencies and release ---
470
+ "dependency_output_delta": {
471
+ "simple": (
472
+ "If updating a dependency could actually change what your system produces - not just "
473
+ "how it's built, but its real output - that update needs evidence showing what changed "
474
+ "and a review, before it merges. A lock file alone only tells you a version number "
475
+ "changed, not what effect that had."
476
+ ),
477
+ "advanced": (
478
+ "A dependency change capable of moving outputs requires delta evidence and review "
479
+ "before merge - the framework's own distinction: a lock file records what changed; "
480
+ "this gate is for what the change actually did."
481
+ ),
482
+ },
483
+ "records_before_release": {
484
+ "simple": (
485
+ "Whatever records your project's risk level says you need - change records, decision "
486
+ "records - they need to actually exist before you start preparing a release, not get "
487
+ "backfilled afterwards to make the release checklist look complete."
488
+ ),
489
+ "advanced": (
490
+ "The change and decision records a change's risk classification requires exist before "
491
+ "release preparation begins - ties back to `risk_classification`, since what's required "
492
+ "here depends on that earlier decision."
493
+ ),
494
+ },
495
+ "change_record_before_completion": {
496
+ "simple": (
497
+ "A change isn't \"done\" just because the code works - it's done once there's a real "
498
+ "record of the change too. This gate checks that the record exists before the change is "
499
+ "treated as finished."
500
+ ),
501
+ "advanced": (
502
+ "A change record exists before the change is treated as complete - closing the gap "
503
+ "where \"done\" quietly comes to mean only \"the code merged\", with the record arriving "
504
+ "later or not at all."
505
+ ),
506
+ },
507
+ }
508
+
509
+ # What "conformance level" itself means, shown once before the level-choice list - the third
510
+ # worked example the maintainer approved, verbatim. The per-level gate/control counts are shown
511
+ # separately, computed from the catalogue (`catalogue.level_summary`), not restated here as static
512
+ # prose that could drift from it.
513
+ LEVEL_CHOICE: dict[str, str] = {
514
+ "simple": (
515
+ "Think of this as choosing how strict your own rules are. essential is lightest - for "
516
+ "something only your own team sees. standard is for anything a colleague or customer "
517
+ "relies on. full is strictest - for things where a wrong number could genuinely hurt "
518
+ "someone."
519
+ ),
520
+ "advanced": (
521
+ "essential: one required gate, one required control. standard: floor grows to "
522
+ "authority_map, decision_before_implementation, change_record_before_completion, plus "
523
+ "four interface gates if UI, plus four controls. full: floor grows further to "
524
+ "regression_before_merge, equivalence_evidence, and four record-based controls."
525
+ ),
526
+ }
527
+
528
+ # `F67`: one short cue per level control, for a list row that has to fit beside a tick box at 80
529
+ # columns. The full explanation is shown for the highlighted row; the cue only has to say which
530
+ # control this is in words a first-time reader recognises. `tests/test_adopt.py` asserts every
531
+ # level control has one and that none exceeds forty characters.
532
+ CUES: dict[str, str] = {
533
+ "dependency_lock": "exact dependency versions, checked",
534
+ "deterministic_tests": "a CI test step that can fail, checked",
535
+ "contract_tests": "tests of what others rely on, checked",
536
+ "documentation_authority": "one map of which document wins",
537
+ "provenance": "trace a result back to its inputs",
538
+ "run_lineage": "reproduce a result from its run",
539
+ "method_registry": "a register of governed methods",
540
+ "overrides": "every manual adjustment on record",
541
+ "assurance_findings": "known limitations written down",
542
+ }
543
+
544
+ EXPLANATIONS: dict[str, dict[str, str]] = {**_BASELINE_CONTROLS, **_LEVEL_CONTROLS, **_GATES}
545
+
546
+
547
+ def explain(item_id: str, mode: str) -> str:
548
+ """The explanation for `item_id` in `mode` ("simple" or "advanced"). Raises `KeyError` on an
549
+ unknown item ID rather than returning a silent empty string - a missing item is a coverage gap
550
+ this module's own test catches, not something to paper over at the call site."""
551
+ return EXPLANATIONS[item_id][mode]