principles 0.0.1

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 (278) hide show
  1. checksums.yaml +7 -0
  2. data/exe/principles +10 -0
  3. data/lib/principles/after_commit_hook_options.rb +44 -0
  4. data/lib/principles/cli.rb +149 -0
  5. data/lib/principles/command_line.rb +188 -0
  6. data/lib/principles/command_routes.rb +60 -0
  7. data/lib/principles/controllers/acknowledgement.rb +106 -0
  8. data/lib/principles/controllers/answer.rb +172 -0
  9. data/lib/principles/controllers/audit.rb +203 -0
  10. data/lib/principles/controllers/audit_completion.rb +113 -0
  11. data/lib/principles/controllers/command.rb +201 -0
  12. data/lib/principles/controllers/correction.rb +99 -0
  13. data/lib/principles/controllers/help.rb +107 -0
  14. data/lib/principles/controllers/inventory.rb +181 -0
  15. data/lib/principles/controllers/mapping.rb +56 -0
  16. data/lib/principles/controllers/member_entries.rb +149 -0
  17. data/lib/principles/controllers/members_a_commit_changed.rb +43 -0
  18. data/lib/principles/controllers/members_to_assess.rb +160 -0
  19. data/lib/principles/controllers/meta_principle_setup.rb +92 -0
  20. data/lib/principles/controllers/repoint.rb +68 -0
  21. data/lib/principles/controllers/running_tool.rb +41 -0
  22. data/lib/principles/data/audit_files.rb +96 -0
  23. data/lib/principles/data/bounded_file.rb +45 -0
  24. data/lib/principles/data/candidate_files_found.rb +105 -0
  25. data/lib/principles/data/command-steps.json +305 -0
  26. data/lib/principles/data/command_steps_file.rb +50 -0
  27. data/lib/principles/data/expected_type.rb +71 -0
  28. data/lib/principles/data/ignored_by_git.rb +54 -0
  29. data/lib/principles/data/json_text.rb +57 -0
  30. data/lib/principles/data/known_fields.rb +42 -0
  31. data/lib/principles/data/linked_path.rb +102 -0
  32. data/lib/principles/data/map_files.rb +104 -0
  33. data/lib/principles/data/principle_id.rb +32 -0
  34. data/lib/principles/data/principles_subfolder.rb +93 -0
  35. data/lib/principles/data/project_files.rb +109 -0
  36. data/lib/principles/data/repository.rb +165 -0
  37. data/lib/principles/data/running_version.rb +44 -0
  38. data/lib/principles/data/schema_check.rb +139 -0
  39. data/lib/principles/data/schemas/acknowledgement-answer.schema.json +25 -0
  40. data/lib/principles/data/schemas/answer-guide.schema.json +99 -0
  41. data/lib/principles/data/schemas/assessment-answer.schema.json +332 -0
  42. data/lib/principles/data/schemas/audit-record.schema.json +277 -0
  43. data/lib/principles/data/schemas/command-steps.schema.json +83 -0
  44. data/lib/principles/data/schemas/correction-answer-guide.schema.json +68 -0
  45. data/lib/principles/data/schemas/correction-answer.schema.json +306 -0
  46. data/lib/principles/data/schemas/member-map.schema.json +39 -0
  47. data/lib/principles/data/schemas/repointing-answer.schema.json +44 -0
  48. data/lib/principles/error.rb +102 -0
  49. data/lib/principles/invocation_options.rb +77 -0
  50. data/lib/principles/models/accepted_loading.rb +36 -0
  51. data/lib/principles/models/acknowledgement.rb +203 -0
  52. data/lib/principles/models/activation.rb +64 -0
  53. data/lib/principles/models/agent_loads.rb +64 -0
  54. data/lib/principles/models/answer_fields.rb +67 -0
  55. data/lib/principles/models/answer_place.rb +53 -0
  56. data/lib/principles/models/answer_text.rb +37 -0
  57. data/lib/principles/models/answered_time.rb +65 -0
  58. data/lib/principles/models/assessment_answer.rb +131 -0
  59. data/lib/principles/models/assessment_basis.rb +47 -0
  60. data/lib/principles/models/assessment_report.rb +62 -0
  61. data/lib/principles/models/audit.rb +200 -0
  62. data/lib/principles/models/audit_record.rb +249 -0
  63. data/lib/principles/models/audit_records.rb +103 -0
  64. data/lib/principles/models/audit_start.rb +96 -0
  65. data/lib/principles/models/audited_member_lines.rb +45 -0
  66. data/lib/principles/models/candidate_files.rb +66 -0
  67. data/lib/principles/models/candidate_inventory.rb +67 -0
  68. data/lib/principles/models/candidate_listing.rb +32 -0
  69. data/lib/principles/models/carried_assessments.rb +76 -0
  70. data/lib/principles/models/check_since_last_audit.rb +61 -0
  71. data/lib/principles/models/cited_files.rb +39 -0
  72. data/lib/principles/models/command_steps.rb +270 -0
  73. data/lib/principles/models/commits_brought_in.rb +27 -0
  74. data/lib/principles/models/commits_since_base.rb +32 -0
  75. data/lib/principles/models/correction_answer.rb +66 -0
  76. data/lib/principles/models/correction_basis.rb +70 -0
  77. data/lib/principles/models/dated_line.rb +29 -0
  78. data/lib/principles/models/decided_prior_findings.rb +106 -0
  79. data/lib/principles/models/declared_map_changes.rb +156 -0
  80. data/lib/principles/models/disposition.rb +233 -0
  81. data/lib/principles/models/entry_file.rb +65 -0
  82. data/lib/principles/models/entry_files.rb +46 -0
  83. data/lib/principles/models/evidence_item.rb +48 -0
  84. data/lib/principles/models/evidence_lines_since.rb +57 -0
  85. data/lib/principles/models/evidence_replacements.rb +99 -0
  86. data/lib/principles/models/finding_write_ups.rb +108 -0
  87. data/lib/principles/models/findings_left_open.rb +34 -0
  88. data/lib/principles/models/first_raised_claim.rb +128 -0
  89. data/lib/principles/models/frontmatter_fields.rb +128 -0
  90. data/lib/principles/models/hook_commands.rb +100 -0
  91. data/lib/principles/models/ignored_candidates.rb +46 -0
  92. data/lib/principles/models/json_digest.rb +35 -0
  93. data/lib/principles/models/json_path_query.rb +131 -0
  94. data/lib/principles/models/json_path_selectors.rb +64 -0
  95. data/lib/principles/models/json_path_string.rb +76 -0
  96. data/lib/principles/models/kept_prior_finding.rb +70 -0
  97. data/lib/principles/models/last_completed_revision.rb +70 -0
  98. data/lib/principles/models/line_span.rb +49 -0
  99. data/lib/principles/models/loaded_files.rb +60 -0
  100. data/lib/principles/models/loading_decision.rb +45 -0
  101. data/lib/principles/models/loading_facts.rb +45 -0
  102. data/lib/principles/models/loads_to_check.rb +93 -0
  103. data/lib/principles/models/locate_refusal.rb +23 -0
  104. data/lib/principles/models/located_text.rb +43 -0
  105. data/lib/principles/models/locator.rb +52 -0
  106. data/lib/principles/models/map_members.rb +171 -0
  107. data/lib/principles/models/markdown_file.rb +92 -0
  108. data/lib/principles/models/member.rb +171 -0
  109. data/lib/principles/models/member_assessments.rb +144 -0
  110. data/lib/principles/models/member_elsewhere.rb +74 -0
  111. data/lib/principles/models/member_file.rb +156 -0
  112. data/lib/principles/models/member_grounds.rb +32 -0
  113. data/lib/principles/models/member_history.rb +68 -0
  114. data/lib/principles/models/member_lines.rb +95 -0
  115. data/lib/principles/models/member_loads.rb +197 -0
  116. data/lib/principles/models/member_map.rb +219 -0
  117. data/lib/principles/models/member_map_history.rb +143 -0
  118. data/lib/principles/models/member_maps.rb +185 -0
  119. data/lib/principles/models/member_moves.rb +95 -0
  120. data/lib/principles/models/member_problems.rb +253 -0
  121. data/lib/principles/models/member_sighting.rb +176 -0
  122. data/lib/principles/models/member_snapshots.rb +36 -0
  123. data/lib/principles/models/member_state.rb +35 -0
  124. data/lib/principles/models/members_elsewhere.rb +46 -0
  125. data/lib/principles/models/members_sharing_lines.rb +116 -0
  126. data/lib/principles/models/meta_principle.rb +51 -0
  127. data/lib/principles/models/missing_files.rb +85 -0
  128. data/lib/principles/models/misspelt_member_id.rb +45 -0
  129. data/lib/principles/models/open_finding.rb +221 -0
  130. data/lib/principles/models/open_findings_elsewhere.rb +45 -0
  131. data/lib/principles/models/other_principles.rb +47 -0
  132. data/lib/principles/models/placement.rb +42 -0
  133. data/lib/principles/models/plain_text_file.rb +32 -0
  134. data/lib/principles/models/principles_folder_move.rb +59 -0
  135. data/lib/principles/models/principles_package.rb +53 -0
  136. data/lib/principles/models/prior_finding_decision.rb +162 -0
  137. data/lib/principles/models/prior_finding_outcomes.rb +60 -0
  138. data/lib/principles/models/prior_findings.rb +109 -0
  139. data/lib/principles/models/problem_report.rb +110 -0
  140. data/lib/principles/models/project.rb +214 -0
  141. data/lib/principles/models/project_history.rb +203 -0
  142. data/lib/principles/models/record_judged_against.rb +42 -0
  143. data/lib/principles/models/recorded_entry.rb +93 -0
  144. data/lib/principles/models/recorded_finding.rb +97 -0
  145. data/lib/principles/models/recorded_findings.rb +58 -0
  146. data/lib/principles/models/recorded_members.rb +149 -0
  147. data/lib/principles/models/repoint.rb +112 -0
  148. data/lib/principles/models/reported_check.rb +130 -0
  149. data/lib/principles/models/request_id.rb +70 -0
  150. data/lib/principles/models/runs_from.rb +36 -0
  151. data/lib/principles/models/skill_loads.rb +123 -0
  152. data/lib/principles/models/still_holds_restatement.rb +87 -0
  153. data/lib/principles/models/text_size.rb +26 -0
  154. data/lib/principles/models/uncommitted_outside_the_map.rb +30 -0
  155. data/lib/principles/models/waits_on.rb +58 -0
  156. data/lib/principles/models/write_up_opening.rb +93 -0
  157. data/lib/principles/models/write_up_pointer.rb +50 -0
  158. data/lib/principles/models/write_up_repoint.rb +39 -0
  159. data/lib/principles/prose_list.rb +23 -0
  160. data/lib/principles/supported_ruby.rb +93 -0
  161. data/lib/principles/version.rb +11 -0
  162. data/lib/principles/views/acknowledged_since_last_audit.rb +34 -0
  163. data/lib/principles/views/acknowledgement_result.rb +44 -0
  164. data/lib/principles/views/activation_in_hand.rb +47 -0
  165. data/lib/principles/views/assessment_request.rb +131 -0
  166. data/lib/principles/views/audit_changes.rb +79 -0
  167. data/lib/principles/views/audit_commit.rb +55 -0
  168. data/lib/principles/views/audit_result.rb +147 -0
  169. data/lib/principles/views/candidate_files_result.rb +95 -0
  170. data/lib/principles/views/check_result_template.rb +75 -0
  171. data/lib/principles/views/diff_since_last_audit.rb +29 -0
  172. data/lib/principles/views/findings_result.rb +45 -0
  173. data/lib/principles/views/help.rb +119 -0
  174. data/lib/principles/views/invocation.rb +66 -0
  175. data/lib/principles/views/landed_since_base.rb +27 -0
  176. data/lib/principles/views/lines_shared_with_member.rb +111 -0
  177. data/lib/principles/views/loads_to_check_result.rb +45 -0
  178. data/lib/principles/views/map_result.rb +169 -0
  179. data/lib/principles/views/member_change_since_baseline.rb +58 -0
  180. data/lib/principles/views/member_repairs.rb +82 -0
  181. data/lib/principles/views/members_a_commit_changed.rb +66 -0
  182. data/lib/principles/views/members_with_no_baseline.rb +56 -0
  183. data/lib/principles/views/meta_principle_passage.rb +23 -0
  184. data/lib/principles/views/meta_principle_result.rb +170 -0
  185. data/lib/principles/views/open_findings_elsewhere_result.rb +32 -0
  186. data/lib/principles/views/open_findings_line.rb +125 -0
  187. data/lib/principles/views/other_principles_unjudged_result.rb +32 -0
  188. data/lib/principles/views/outside_the_map_result.rb +31 -0
  189. data/lib/principles/views/package_since_last_audit_result.rb +34 -0
  190. data/lib/principles/views/principle_read_result.rb +86 -0
  191. data/lib/principles/views/principle_with_changed_members.rb +26 -0
  192. data/lib/principles/views/principles_with_no_completed_audit.rb +30 -0
  193. data/lib/principles/views/printed_principle.rb +45 -0
  194. data/lib/principles/views/prior_finding_decisions_result.rb +35 -0
  195. data/lib/principles/views/prior_findings_result.rb +80 -0
  196. data/lib/principles/views/problem_lines.rb +84 -0
  197. data/lib/principles/views/problem_result.rb +107 -0
  198. data/lib/principles/views/prompts/acknowledge-a-change/say-whether-the-change-keeps-the-meaning.md +12 -0
  199. data/lib/principles/views/prompts/answer-any-judgement-request/what-a-self-audit-cannot-show.md +6 -0
  200. data/lib/principles/views/prompts/assess-every-member/answer-guide.json +63 -0
  201. data/lib/principles/views/prompts/assess-every-member/assess-each-member-and-the-set.md +12 -0
  202. data/lib/principles/views/prompts/assess-every-member/check-roles-other-principles-give-these-lines.md +7 -0
  203. data/lib/principles/views/prompts/assess-every-member/decide-each-member-loading.md +17 -0
  204. data/lib/principles/views/prompts/assess-every-member/decide-each-prior-finding.md +38 -0
  205. data/lib/principles/views/prompts/assess-every-member/give-problem-evidence-at-still-holds.md +2 -0
  206. data/lib/principles/views/prompts/assess-every-member/give-the-evidence-a-prior-finding-lacks.md +7 -0
  207. data/lib/principles/views/prompts/assess-every-member/have-a-changed-check-reviewed-before-running-it.md +4 -0
  208. data/lib/principles/views/prompts/assess-every-member/have-a-new-check-reviewed-before-running-it.md +3 -0
  209. data/lib/principles/views/prompts/assess-every-member/have-hidden-characters-in-a-check-explained.md +4 -0
  210. data/lib/principles/views/prompts/assess-every-member/judge-acknowledged-edits-together.md +6 -0
  211. data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-against-the-other-principles.md +7 -0
  212. data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-under-the-release-since-the-last-audit.md +10 -0
  213. data/lib/principles/views/prompts/assess-every-member/judge-the-set-with-carried-assessments.md +10 -0
  214. data/lib/principles/views/prompts/assess-every-member/keep-the-last-review-basis-unless-you-give-a-new-one.md +1 -0
  215. data/lib/principles/views/prompts/assess-every-member/name-another-principles-open-finding-rather-than-raise-it.md +9 -0
  216. data/lib/principles/views/prompts/assess-every-member/read-whether-each-prior-finding-s-evidence-changed.md +12 -0
  217. data/lib/principles/views/prompts/assess-every-member/run-a-named-check-at-every-full-audit.md +2 -0
  218. data/lib/principles/views/prompts/assess-every-member/run-the-check-then-report-it.md +2 -0
  219. data/lib/principles/views/prompts/assess-every-member/run-the-reviewed-check-and-say-what-its-review-rested-on.md +6 -0
  220. data/lib/principles/views/prompts/assess-every-member/uncommitted-files-outside-the-map.md +4 -0
  221. data/lib/principles/views/prompts/assess-every-member/why-each-member-is-judged-again.md +24 -0
  222. data/lib/principles/views/prompts/dispose-of-each-finding/answer-guide.json +34 -0
  223. data/lib/principles/views/prompts/dispose-of-each-finding/fix-or-record-each-finding.md +24 -0
  224. data/lib/principles/views/prompts/find-a-principle-to-map/ask-the-owner-before-mapping.md +1 -0
  225. data/lib/principles/views/prompts/find-a-principle-to-map/choose-one-principle.md +5 -0
  226. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored-folders.md +2 -0
  227. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored.md +3 -0
  228. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-possibly-test-data.md +2 -0
  229. data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-untracked.md +2 -0
  230. data/lib/principles/views/prompts/find-a-principle-to-map/what-each-command-writes.md +4 -0
  231. data/lib/principles/views/prompts/find-a-principle-to-map/where-the-inventory-looks.md +10 -0
  232. data/lib/principles/views/prompts/learn-what-a-command-does/add-member.md +45 -0
  233. data/lib/principles/views/prompts/learn-what-a-command-does/change-activation.md +21 -0
  234. data/lib/principles/views/prompts/learn-what-a-command-does/change-description.md +2 -0
  235. data/lib/principles/views/prompts/learn-what-a-command-does/change-member-check.md +17 -0
  236. data/lib/principles/views/prompts/learn-what-a-command-does/change-member-role.md +9 -0
  237. data/lib/principles/views/prompts/learn-what-a-command-does/create-principle.md +6 -0
  238. data/lib/principles/views/prompts/learn-what-a-command-does/every-command-and-how-they-chain.md +28 -0
  239. data/lib/principles/views/prompts/learn-what-a-command-does/list-candidate-files.md +17 -0
  240. data/lib/principles/views/prompts/learn-what-a-command-does/list-member-problems.md +73 -0
  241. data/lib/principles/views/prompts/learn-what-a-command-does/list-open-findings.md +71 -0
  242. data/lib/principles/views/prompts/learn-what-a-command-does/list-principles.md +7 -0
  243. data/lib/principles/views/prompts/learn-what-a-command-does/move-member.md +11 -0
  244. data/lib/principles/views/prompts/learn-what-a-command-does/remove-member-check.md +10 -0
  245. data/lib/principles/views/prompts/learn-what-a-command-does/remove-member.md +6 -0
  246. data/lib/principles/views/prompts/learn-what-a-command-does/show-answer-check.md +16 -0
  247. data/lib/principles/views/prompts/learn-what-a-command-does/show-audit.md +20 -0
  248. data/lib/principles/views/prompts/learn-what-a-command-does/show-meta-principle-passage.md +52 -0
  249. data/lib/principles/views/prompts/learn-what-a-command-does/show-principle.md +86 -0
  250. data/lib/principles/views/prompts/learn-what-a-command-does/start-audit.md +68 -0
  251. data/lib/principles/views/prompts/learn-what-a-command-does/submit-answer.md +56 -0
  252. data/lib/principles/views/prompts/repoint-write-ups/point-each-moved-write-up-at-its-new-place.md +10 -0
  253. data/lib/principles/views/prompts/report-after-a-commit/it-refused-after-the-commits-touched-the-maps.md +2 -0
  254. data/lib/principles/views/prompts/report-after-a-commit/others-are-still-listed.md +1 -0
  255. data/lib/principles/views/prompts/report-after-a-commit/the-commits-changed-these-members.md +2 -0
  256. data/lib/principles/views/prompts/report-after-a-commit/this-command-shows-each-one.md +1 -0
  257. data/lib/principles/views/prompts/report-the-completed-audit/commit-only-what-the-audit-changed.md +2 -0
  258. data/lib/principles/views/prompts/report-the-completed-audit/replace-the-default-commit-message.md +4 -0
  259. data/lib/principles/views/prompts/report-the-completed-audit/what-completion-means.md +2 -0
  260. data/lib/principles/views/prompts/set-principles-up/entry-file-passage.md +3 -0
  261. data/lib/principles/views/prompts/set-principles-up/name-the-step-each-member-lacks.md +15 -0
  262. data/lib/principles/views/prompts/set-principles-up/place-the-passage-with-the-user.md +31 -0
  263. data/lib/principles/views/prompts/set-principles-up/set-up-first.md +2 -0
  264. data/lib/principles/views/prompts/set-principles-up/what-the-passage-presumes.md +4 -0
  265. data/lib/principles/views/prompts/start-a-session/act-on-the-open-findings.md +5 -0
  266. data/lib/principles/views/prompts/start-a-session/it-returns-after-each-clear-or-compaction-too.md +1 -0
  267. data/lib/principles/views/prompts/start-a-session/it-returns-at-each-session-start.md +1 -0
  268. data/lib/principles/views/prompts/start-a-session/raise-them-at-the-next-pause.md +1 -0
  269. data/lib/principles/views/prompts/start-a-session/raise-them-before-the-first-task.md +1 -0
  270. data/lib/principles/views/prompts.rb +71 -0
  271. data/lib/principles/views/repoint_result.rb +80 -0
  272. data/lib/principles/views/request_part.rb +26 -0
  273. data/lib/principles/views/request_parts.rb +26 -0
  274. data/lib/principles/views/shell_word.rb +29 -0
  275. data/lib/principles/views/visible_text.rb +26 -0
  276. data/lib/principles/views/workflow_diagram.rb +77 -0
  277. data/lib/principles.rb +5 -0
  278. metadata +326 -0
@@ -0,0 +1,131 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../error"
5
+ require_relative "answer_fields"
6
+ require_relative "loads_to_check"
7
+ require_relative "member_assessments"
8
+ require_relative "misspelt_member_id"
9
+ require_relative "record_judged_against"
10
+
11
+ module Principles
12
+ # The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
13
+ module Models
14
+ # An agent's answer to an assessment request: one assessment per member, an assessment of the set, a
15
+ # recent decision under it, and findings. The block locates each piece of evidence in the project and
16
+ # refuses one that does not resolve. members are the audited members, each checked one's assessment reporting
17
+ # its check's result (D30); judged_against is the principle's record as it stands, which lists the members
18
+ # whose loading it must decide (#355) and the assessments it may carry (D49); answered_at is when it came, which
19
+ # no sighting it reports is after (#699).
20
+ class AssessmentAnswer
21
+ #: (untyped, MapMembers, answered_at: String, ?judged_against: RecordJudgedAgainst?)
22
+ #| { (Locator) -> Hash[String, untyped] } -> void
23
+ def initialize(response, members, answered_at:, judged_against: nil, &locate)
24
+ @response = response
25
+ @members = members
26
+ @answered_at = answered_at
27
+ @judged_against = judged_against
28
+ @loads = judged_against&.loads #: LoadsToCheck?
29
+ @locate = locate #: ^(Locator) -> Hash[String, untyped]
30
+ @fields = AnswerFields.new(locate) #: AnswerFields
31
+ end
32
+
33
+ # The answer's findings, once every member assessment, the collective assessment, the recent
34
+ # decision and each finding have been checked.
35
+ #: () -> Array[untyped]
36
+ def findings
37
+ member_assessments.check
38
+ @fields.text(@response["collective_assessment"], "collective_assessment")
39
+ recent_decision
40
+ checked_findings
41
+ end
42
+
43
+ # The result reported for each checked member's check, by member id, once each has been checked.
44
+ #: () -> Hash[String, Hash[String, untyped]]
45
+ def check_results = member_assessments.check_results
46
+
47
+ # By member id, the fields this answer reports for each member's record entry: its assessment, which the next
48
+ # full audit may carry forward (D49), its check's result (D30) and its accepted loading decision (#355), once
49
+ # each listed member's is checked against loads.
50
+ #: () -> Hash[String, Hash[String, untyped]]
51
+ def entry_fields
52
+ checked = check_results.transform_values do |result|
53
+ { "check_result" => result }
54
+ end #: Hash[String, Hash[String, untyped]]
55
+ accepted = (@loads&.decided(@response["loading_decisions"]) || {}).transform_values do |kept|
56
+ { "loading_accepted" => kept }
57
+ end
58
+ reported = checked.merge(accepted) { |_id, check, loading| check.merge(loading) }
59
+ member_assessments.kept(&@locate).merge(reported) { |_id, assessment, fields| assessment.merge(fields) }
60
+ end
61
+
62
+ # The paths the recent decision's evidence cites, which the record does not keep but a completed audit asks to
63
+ # commit when they differ from HEAD (#860); none when the decision was unavailable.
64
+ #: () -> Array[String]
65
+ def recent_decision_paths
66
+ decision = @response.fetch("recent_decision")
67
+ return [] if decision["unavailable_reason"]
68
+
69
+ decision.fetch("evidence").map { |item| item.fetch("path") }.uniq
70
+ end
71
+
72
+ private
73
+
74
+ #: () -> MemberAssessments
75
+ def member_assessments
76
+ MemberAssessments.new(@response["member_assessments"], @members, fields: @fields,
77
+ judged_against: @judged_against,
78
+ answered_at: @answered_at)
79
+ end
80
+
81
+ #: () -> void
82
+ def recent_decision
83
+ decision = @response.fetch("recent_decision")
84
+ if decision["unavailable_reason"]
85
+ @fields.text(decision["unavailable_reason"], "recent_decision.unavailable_reason")
86
+ else
87
+ @fields.text(decision["assessment"], "recent_decision.assessment")
88
+ @fields.evidence(decision["evidence"], AnswerPlace.new("/recent_decision/evidence", "Recent decision"))
89
+ end
90
+ end
91
+
92
+ #: () -> Array[untyped]
93
+ def checked_findings
94
+ findings = @fields.array(@response["findings"], "findings")
95
+ finding_ids = findings.each_with_index.map { |finding, index| finding_id(finding, index) }
96
+ unless finding_ids.uniq == finding_ids
97
+ raise Error.new("duplicate_finding", "Finding ids repeat",
98
+ "Give each finding a unique id.")
99
+ end
100
+
101
+ findings
102
+ end
103
+
104
+ # The id of finding, once its fields, members and evidence have been checked.
105
+ #: (untyped, Integer) -> untyped
106
+ def finding_id(finding, index)
107
+ @fields.text(finding["id"], "finding.id")
108
+ %w[problem consequence proposed_action].each { |field| @fields.text(finding[field], "finding.#{field}") }
109
+ affected_members(finding)
110
+ place = AnswerPlace.new("/findings/#{index}", "Finding #{finding["id"]}")
111
+ @fields.evidence(finding["evidence"], place.child("evidence"))
112
+ finding.fetch("id")
113
+ end
114
+
115
+ # Refuses a finding that names no member, or one the map does not have.
116
+ #: (untyped) -> void
117
+ def affected_members(finding)
118
+ affected = @fields.array(finding["member_ids"], "finding.member_ids")
119
+ ids = @members.ids
120
+ return unless affected.empty? || (affected - ids).any?
121
+
122
+ raise Error.new("unknown_member", "Finding #{finding["id"]} names unknown or no members",
123
+ [*near_misses(affected - ids), "Use one or more of #{ids.join(", ")}."].join(" "))
124
+ end
125
+
126
+ # A hint for each unmapped id that differs from a mapped one only in punctuation, case or spacing.
127
+ #: (Array[untyped]) -> Array[String]
128
+ def near_misses(unknown) = unknown.grep(String).filter_map { |id| MisspeltMemberId.new(id).hint(@members.ids) }
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "audit_record"
5
+ require_relative "last_completed_revision"
6
+
7
+ module Principles
8
+ # The domain logic, which loads and saves itself through data/. models/CLAUDE.md owns the boundary.
9
+ module Models
10
+ # What an assessment request prints as its basis, which the agent hands back with the answer: the commit the
11
+ # audit is based on, nil outside git, and the revision of the last completed audit record when the request was
12
+ # printed, nil when there was none, so the answer is refused once that record changed (#653, D50).
13
+ class AssessmentBasis
14
+ # The basis printed for an audit based on base, after record, the principle's audit record now, if any.
15
+ #: (String?, AuditRecord?) -> AssessmentBasis
16
+ def self.printed(base, record)
17
+ new("base_commit" => base, "last_completed_revision" => record&.last_completed&.revision)
18
+ end
19
+
20
+ #: (Hash[String, untyped]) -> void
21
+ def initialize(fields)
22
+ @fields = fields
23
+ freeze
24
+ end
25
+
26
+ #: () -> Hash[String, untyped]
27
+ def to_h = @fields
28
+
29
+ # The commit the audit is based on, nil outside git.
30
+ #: () -> String?
31
+ def base = @fields["base_commit"]
32
+
33
+ # The last completed record's revision when the request was printed, nil when there was none, which the
34
+ # correction the assessment prints carries forward (D50).
35
+ #: () -> String?
36
+ def revision = @fields["last_completed_revision"]
37
+
38
+ # The last completed record's revision when the request was printed, which refuses the answer once it changed.
39
+ #: () -> LastCompletedRevision
40
+ def printed_after = LastCompletedRevision.new(revision)
41
+
42
+ # What the request id digests of the basis, in a fixed order, so how the answer orders its keys changes nothing.
43
+ #: () -> Array[String?]
44
+ def digested = [base, revision]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,62 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "assessment_answer"
5
+ require_relative "member_grounds"
6
+ require_relative "member_moves"
7
+
8
+ module Principles
9
+ # The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
10
+ module Models
11
+ # What an assessment reported beyond its findings, which its audit's record and commit carry: by member id, the
12
+ # fields of each member's record entry, such as its assessment (D49) and its check's result (D30), and the paths
13
+ # its recent decision cites, which the record does not keep but the completed audit asks to commit when they
14
+ # differ from HEAD (#860). A correction request carries it in its basis, so the completed audit keeps it; this is
15
+ # the one owner of how it is carried.
16
+ class AssessmentReport
17
+ # The report of the answer assessed, each assessment it makes, rather than carries, with the grounds it was
18
+ # judged on (D49).
19
+ #: (AssessmentAnswer, MemberGrounds) -> AssessmentReport
20
+ def self.answered(assessed, grounds)
21
+ fields = assessed.entry_fields.to_h do |id, entry|
22
+ kept = entry["assessment"]
23
+ [id, kept && !kept["carried_forward"] ? entry.merge("assessment" => kept.merge(grounds.of(id))) : entry]
24
+ end
25
+ new(fields, recent_decision_paths: assessed.recent_decision_paths)
26
+ end
27
+
28
+ # The report carried in a correction basis, under the ids of any members moves moved.
29
+ #: (CorrectionBasis, MemberMoves) -> AssessmentReport
30
+ def self.carried(basis, moves)
31
+ fields = basis.to_h
32
+ new(moves.hashes(fields.fetch("entry_fields", {})),
33
+ recent_decision_paths: fields.fetch("recent_decision_paths", []))
34
+ end
35
+
36
+ # The paths the recent decision cites as evidence.
37
+ #: Array[String]
38
+ attr_reader :recent_decision_paths
39
+
40
+ # entry_fields are, by member id, the fields the assessment reported for that member's record entry.
41
+ #: (?Hash[String, Hash[String, untyped]], ?recent_decision_paths: Array[String]) -> void
42
+ def initialize(entry_fields = {}, recent_decision_paths: [])
43
+ @entry_fields = entry_fields #: Hash[String, Hash[String, untyped]]
44
+ @recent_decision_paths = recent_decision_paths
45
+ freeze
46
+ end
47
+
48
+ # The fields the assessment reported for the record entry of the member known by id, or none.
49
+ #: (String) -> Hash[String, untyped]
50
+ def entry_fields_of(id) = @entry_fields.fetch(id, {})
51
+
52
+ # The fields a correction basis carries it in, leaving out what it does not hold.
53
+ #: () -> Hash[String, untyped]
54
+ def basis_fields
55
+ { "entry_fields" => @entry_fields, "recent_decision_paths" => @recent_decision_paths }
56
+ .reject { |_key, value| value.empty? }
57
+ end
58
+
59
+ NONE = new #: AssessmentReport
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,200 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../error"
5
+ require_relative "member_map"
6
+ require_relative "declared_map_changes"
7
+ require_relative "request_id"
8
+ require_relative "correction_basis"
9
+ require_relative "assessment_basis"
10
+ require_relative "assessment_answer"
11
+ require_relative "audit_start"
12
+ require_relative "assessment_report"
13
+ require_relative "loads_to_check"
14
+ require_relative "member_grounds"
15
+ require_relative "carried_assessments"
16
+ require_relative "prior_findings"
17
+ require_relative "decided_prior_findings"
18
+ require_relative "disposition"
19
+
20
+ module Principles
21
+ # The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
22
+ module Models
23
+ # One principle's audit as its map and members are now. Nothing about a request is stored (D25): each
24
+ # request id digests what the request depends on, so an answer is refused when what it was asked about
25
+ # has changed since. A correction is based on the map's version and each finding's members, never on
26
+ # the agent's prose.
27
+ class Audit
28
+ #: MemberMap
29
+ attr_reader :map
30
+ # Each member's hash now.
31
+ #: Hash[String, String]
32
+ attr_reader :now
33
+
34
+ #: (map: MemberMap, now: Hash[String, String]) -> void
35
+ def initialize(map:, now:)
36
+ @map = map
37
+ @now = now
38
+ end
39
+
40
+ # The principle audited, as its map names it.
41
+ #: () -> String
42
+ def id = @map.id
43
+
44
+ #: () -> String
45
+ def map_version = @map.version
46
+
47
+ # The id of the assessment request printed with basis: its base commit, nil outside git, and the last
48
+ # completed record's revision, so an answer cannot point the request at a later record (#653).
49
+ # prior is what PriorFindings#digested gives, empty when the last audit left no finding to decide (D35);
50
+ # loads is what LoadsToCheck#listed gives, nil when the activation lists nothing to check (#355).
51
+ #: (AssessmentBasis, ?Array[Hash[String, untyped]], ?Hash[String, untyped]?) -> RequestId
52
+ def assessment_id(basis, prior = [], loads = nil)
53
+ parts = [map_version, basis.digested, @now]
54
+ parts = [*parts, prior] unless prior.empty?
55
+ parts = [*parts, { "loads_to_check" => loads }] if loads
56
+ RequestId.for(principle_id: id, phase: "assessment", parts: parts)
57
+ end
58
+
59
+ # Refuses an assessment unless request_id is the one printed with basis for the map and members as they are
60
+ # now, and, in git, the members are as its base holds them (committed).
61
+ # It returns the findings the last audit, whose record is given, left for this one to decide (D35); loads is
62
+ # what the audit lists to check now, so a change to what lists a member to check makes the request stale (#355).
63
+ #: (RequestId, basis: AssessmentBasis, committed: Hash[String, String]?, record: AuditRecord?,
64
+ #| loads: LoadsToCheck) -> PriorFindings
65
+ def check_assessment_request(request_id, basis:, committed:, record:, loads:)
66
+ prior = prior_findings(record)
67
+ now_id = assessment_id(basis, prior.digested, loads.listed)
68
+ raise stale_request unless now_id == request_id && (basis.base.nil? || committed == @now)
69
+
70
+ prior
71
+ end
72
+
73
+ # The answer to this audit's assessment request, checked against each member, each member's check (D30)
74
+ # and each member the listing names to check against the activation (#355); the block locates each piece of
75
+ # evidence, giving where it is and the hash of its text.
76
+ # judged_against is the principle's record as it stands, which says which members' last assessments the
77
+ # answer may carry forward (D49); answered_at is when the answer came, which no sighting it reports is after
78
+ # (#699).
79
+ #: (untyped, RecordJudgedAgainst, String) { (Locator) -> Hash[String, untyped] }
80
+ #| -> AssessmentAnswer
81
+ def assessment(response, judged_against, answered_at, &)
82
+ AssessmentAnswer.new(response, map_members, answered_at: answered_at, judged_against: judged_against, &)
83
+ end
84
+
85
+ # A digest of what this audit's assessments answer: its description and question (D49).
86
+ #: (String) -> String
87
+ def judged_against(question) = started(nil, {}).judged_against(question)
88
+
89
+ # What this audit began from: base (nil outside git), each member's hash then and what the assessment reported
90
+ # beyond its findings.
91
+ #: (String?, Hash[String, String], ?AssessmentReport) -> AuditStart
92
+ def started(base, before, reported = AssessmentReport::NONE)
93
+ AuditStart.new(map: @map, base: base, before: before, reported: reported)
94
+ end
95
+
96
+ # What an audit answered by assessed began from: base, each member's hash now, and what the answer reported
97
+ # beyond its findings, each assessment with the grounds it was judged on (D49).
98
+ #: (String?, AssessmentAnswer, MemberGrounds) -> AuditStart
99
+ def assessed_start(base, assessed, grounds)
100
+ started(base, @now, AssessmentReport.answered(assessed, grounds))
101
+ end
102
+
103
+ # What the audit a correction's basis names began from: its base commit, each member's hash then
104
+ # (before), and the assessment's report the basis carries, under the ids of any moved members.
105
+ #: (CorrectionBasis, Hash[String, String], MemberMoves) -> AuditStart
106
+ def resumed(basis, before, moves)
107
+ started(basis.base_commit, before, AssessmentReport.carried(basis, moves))
108
+ end
109
+
110
+ # The map the audit began from: the current one when it is unchanged and the answer declares no change; or,
111
+ # in git, the map at the base commit, which the block reads, refused unless the declared changes and any
112
+ # members added explain every change. Outside git, a map that changed is refused.
113
+ #: (CorrectionBasis, DeclaredMapChanges, map_file: String) { (String) -> MemberMap } -> MemberMap
114
+ def map_started_from(basis, changes, map_file:, &)
115
+ base = changes.base_to_read(basis.base_commit, @map, map_version: basis.map_version, map_file: map_file)
116
+ return @map unless base
117
+
118
+ started = yield(base)
119
+ changes.check(started, @map, map_version: basis.map_version, map_file: map_file)
120
+ started
121
+ end
122
+
123
+ # The hashes the audit began from (before), refused unless they were found and request_id is the one
124
+ # printed for them and basis.
125
+ #: (RequestId, CorrectionBasis, Hash[String, String]?) -> Hash[String, String]
126
+ def confirmed_start(request_id, basis, before)
127
+ raise stale_request unless before && basis.request_id(id, before) == request_id
128
+
129
+ before
130
+ end
131
+
132
+ # Each finding's outcome: its members and its disposition's action, where it is recorded, if anywhere, and
133
+ # what a deferral says it waits on, if anything (D52).
134
+ # Then each prior finding kept (D35); a new finding that raises one again keeps when it was first raised.
135
+ #: (Array[untyped], Array[Disposition], ?DecidedPriorFindings) -> Array[untyped]
136
+ def outcomes(findings, dispositions, prior = DecidedPriorFindings::NONE)
137
+ findings.map do |finding|
138
+ id = finding.fetch("id")
139
+ disposition = dispositions.find { |each| each.finding_id == id } or raise KeyError, id
140
+ finding.slice(*RecordedFinding::RAISED_FIELDS).merge(disposition.recorded_outcome, prior.first_raised(id))
141
+ end + prior.kept
142
+ end
143
+
144
+ # What each member's assessment is judged on beside its text, from each member's loads: its map entry and when
145
+ # it loads (D49).
146
+ #: (Hash[String, MemberLoads]) -> MemberGrounds
147
+ def grounds(loads) = MemberGrounds.new(map_members, loads)
148
+
149
+ # Which members' last assessments this audit carries forward, from the principle's record now, grounds, and the
150
+ # question the request asks (D49).
151
+ #: (AuditRecord?, grounds: MemberGrounds, question: String) -> CarriedAssessments
152
+ def carried_assessments(record, grounds:, question:)
153
+ CarriedAssessments.new(record, members: map_members, judged_against: judged_against(question), now: @now,
154
+ grounds: grounds)
155
+ end
156
+
157
+ # The members this audit lists to check against the map's activation, from each member's loads (#355), each
158
+ # with the decision the last completed audit, whose record is given, accepted on its loading (#502).
159
+ #: (Hash[String, MemberLoads], ?AuditRecord?) -> LoadsToCheck
160
+ def loads_to_check(loads, record = nil)
161
+ completed = record&.last_completed
162
+ LoadsToCheck.new(activation: @map.activation, loads: loads, hashes: @now,
163
+ accepted: completed ? completed.members.loading_accepted(map_members) : {})
164
+ end
165
+
166
+ # The findings the last audit left for this one to decide (D35), from its record.
167
+ #: (AuditRecord?) -> PriorFindings
168
+ def prior_findings(record)
169
+ PriorFindings.new(record: record, now: @now, members: map_members)
170
+ end
171
+
172
+ # The project files the dispositions say changed: each fixed member's file, then each file where a
173
+ # finding is recorded.
174
+ #: (Array[Disposition]) -> Array[String]
175
+ def changed_files(dispositions)
176
+ fixed = dispositions.select(&:fixed?).flat_map(&:changed_members).map { |member| path_of(member) }
177
+ (fixed + dispositions.filter_map(&:recorded_path)).uniq
178
+ end
179
+
180
+ # The path of the member with member_id in the map, refused as unknown_member when no member has it (#778).
181
+ #: (String) -> String
182
+ def path_of(member_id) = map_members.found(member_id).path
183
+
184
+ private
185
+
186
+ # The members as the map lists them, which say each one's id and check.
187
+ #: () -> MapMembers
188
+ def map_members = @map.members
189
+
190
+ #: () -> Error
191
+ def stale_request
192
+ Error.new("stale_request", "This request no longer matches #{id}: its map, a member, the commit it " \
193
+ "was based on, or what says when a listed member loads and how big it is " \
194
+ "(project settings, frontmatter or a listed file), has changed since it was " \
195
+ "printed",
196
+ "Start the audit again and answer the new request: #{Error::EXECUTABLE} start-audit #{id}")
197
+ end
198
+ end
199
+ end
200
+ end