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.
- checksums.yaml +7 -0
- data/exe/principles +10 -0
- data/lib/principles/after_commit_hook_options.rb +44 -0
- data/lib/principles/cli.rb +149 -0
- data/lib/principles/command_line.rb +188 -0
- data/lib/principles/command_routes.rb +60 -0
- data/lib/principles/controllers/acknowledgement.rb +106 -0
- data/lib/principles/controllers/answer.rb +172 -0
- data/lib/principles/controllers/audit.rb +203 -0
- data/lib/principles/controllers/audit_completion.rb +113 -0
- data/lib/principles/controllers/command.rb +201 -0
- data/lib/principles/controllers/correction.rb +99 -0
- data/lib/principles/controllers/help.rb +107 -0
- data/lib/principles/controllers/inventory.rb +181 -0
- data/lib/principles/controllers/mapping.rb +56 -0
- data/lib/principles/controllers/member_entries.rb +149 -0
- data/lib/principles/controllers/members_a_commit_changed.rb +43 -0
- data/lib/principles/controllers/members_to_assess.rb +160 -0
- data/lib/principles/controllers/meta_principle_setup.rb +92 -0
- data/lib/principles/controllers/repoint.rb +68 -0
- data/lib/principles/controllers/running_tool.rb +41 -0
- data/lib/principles/data/audit_files.rb +96 -0
- data/lib/principles/data/bounded_file.rb +45 -0
- data/lib/principles/data/candidate_files_found.rb +105 -0
- data/lib/principles/data/command-steps.json +305 -0
- data/lib/principles/data/command_steps_file.rb +50 -0
- data/lib/principles/data/expected_type.rb +71 -0
- data/lib/principles/data/ignored_by_git.rb +54 -0
- data/lib/principles/data/json_text.rb +57 -0
- data/lib/principles/data/known_fields.rb +42 -0
- data/lib/principles/data/linked_path.rb +102 -0
- data/lib/principles/data/map_files.rb +104 -0
- data/lib/principles/data/principle_id.rb +32 -0
- data/lib/principles/data/principles_subfolder.rb +93 -0
- data/lib/principles/data/project_files.rb +109 -0
- data/lib/principles/data/repository.rb +165 -0
- data/lib/principles/data/running_version.rb +44 -0
- data/lib/principles/data/schema_check.rb +139 -0
- data/lib/principles/data/schemas/acknowledgement-answer.schema.json +25 -0
- data/lib/principles/data/schemas/answer-guide.schema.json +99 -0
- data/lib/principles/data/schemas/assessment-answer.schema.json +332 -0
- data/lib/principles/data/schemas/audit-record.schema.json +277 -0
- data/lib/principles/data/schemas/command-steps.schema.json +83 -0
- data/lib/principles/data/schemas/correction-answer-guide.schema.json +68 -0
- data/lib/principles/data/schemas/correction-answer.schema.json +306 -0
- data/lib/principles/data/schemas/member-map.schema.json +39 -0
- data/lib/principles/data/schemas/repointing-answer.schema.json +44 -0
- data/lib/principles/error.rb +102 -0
- data/lib/principles/invocation_options.rb +77 -0
- data/lib/principles/models/accepted_loading.rb +36 -0
- data/lib/principles/models/acknowledgement.rb +203 -0
- data/lib/principles/models/activation.rb +64 -0
- data/lib/principles/models/agent_loads.rb +64 -0
- data/lib/principles/models/answer_fields.rb +67 -0
- data/lib/principles/models/answer_place.rb +53 -0
- data/lib/principles/models/answer_text.rb +37 -0
- data/lib/principles/models/answered_time.rb +65 -0
- data/lib/principles/models/assessment_answer.rb +131 -0
- data/lib/principles/models/assessment_basis.rb +47 -0
- data/lib/principles/models/assessment_report.rb +62 -0
- data/lib/principles/models/audit.rb +200 -0
- data/lib/principles/models/audit_record.rb +249 -0
- data/lib/principles/models/audit_records.rb +103 -0
- data/lib/principles/models/audit_start.rb +96 -0
- data/lib/principles/models/audited_member_lines.rb +45 -0
- data/lib/principles/models/candidate_files.rb +66 -0
- data/lib/principles/models/candidate_inventory.rb +67 -0
- data/lib/principles/models/candidate_listing.rb +32 -0
- data/lib/principles/models/carried_assessments.rb +76 -0
- data/lib/principles/models/check_since_last_audit.rb +61 -0
- data/lib/principles/models/cited_files.rb +39 -0
- data/lib/principles/models/command_steps.rb +270 -0
- data/lib/principles/models/commits_brought_in.rb +27 -0
- data/lib/principles/models/commits_since_base.rb +32 -0
- data/lib/principles/models/correction_answer.rb +66 -0
- data/lib/principles/models/correction_basis.rb +70 -0
- data/lib/principles/models/dated_line.rb +29 -0
- data/lib/principles/models/decided_prior_findings.rb +106 -0
- data/lib/principles/models/declared_map_changes.rb +156 -0
- data/lib/principles/models/disposition.rb +233 -0
- data/lib/principles/models/entry_file.rb +65 -0
- data/lib/principles/models/entry_files.rb +46 -0
- data/lib/principles/models/evidence_item.rb +48 -0
- data/lib/principles/models/evidence_lines_since.rb +57 -0
- data/lib/principles/models/evidence_replacements.rb +99 -0
- data/lib/principles/models/finding_write_ups.rb +108 -0
- data/lib/principles/models/findings_left_open.rb +34 -0
- data/lib/principles/models/first_raised_claim.rb +128 -0
- data/lib/principles/models/frontmatter_fields.rb +128 -0
- data/lib/principles/models/hook_commands.rb +100 -0
- data/lib/principles/models/ignored_candidates.rb +46 -0
- data/lib/principles/models/json_digest.rb +35 -0
- data/lib/principles/models/json_path_query.rb +131 -0
- data/lib/principles/models/json_path_selectors.rb +64 -0
- data/lib/principles/models/json_path_string.rb +76 -0
- data/lib/principles/models/kept_prior_finding.rb +70 -0
- data/lib/principles/models/last_completed_revision.rb +70 -0
- data/lib/principles/models/line_span.rb +49 -0
- data/lib/principles/models/loaded_files.rb +60 -0
- data/lib/principles/models/loading_decision.rb +45 -0
- data/lib/principles/models/loading_facts.rb +45 -0
- data/lib/principles/models/loads_to_check.rb +93 -0
- data/lib/principles/models/locate_refusal.rb +23 -0
- data/lib/principles/models/located_text.rb +43 -0
- data/lib/principles/models/locator.rb +52 -0
- data/lib/principles/models/map_members.rb +171 -0
- data/lib/principles/models/markdown_file.rb +92 -0
- data/lib/principles/models/member.rb +171 -0
- data/lib/principles/models/member_assessments.rb +144 -0
- data/lib/principles/models/member_elsewhere.rb +74 -0
- data/lib/principles/models/member_file.rb +156 -0
- data/lib/principles/models/member_grounds.rb +32 -0
- data/lib/principles/models/member_history.rb +68 -0
- data/lib/principles/models/member_lines.rb +95 -0
- data/lib/principles/models/member_loads.rb +197 -0
- data/lib/principles/models/member_map.rb +219 -0
- data/lib/principles/models/member_map_history.rb +143 -0
- data/lib/principles/models/member_maps.rb +185 -0
- data/lib/principles/models/member_moves.rb +95 -0
- data/lib/principles/models/member_problems.rb +253 -0
- data/lib/principles/models/member_sighting.rb +176 -0
- data/lib/principles/models/member_snapshots.rb +36 -0
- data/lib/principles/models/member_state.rb +35 -0
- data/lib/principles/models/members_elsewhere.rb +46 -0
- data/lib/principles/models/members_sharing_lines.rb +116 -0
- data/lib/principles/models/meta_principle.rb +51 -0
- data/lib/principles/models/missing_files.rb +85 -0
- data/lib/principles/models/misspelt_member_id.rb +45 -0
- data/lib/principles/models/open_finding.rb +221 -0
- data/lib/principles/models/open_findings_elsewhere.rb +45 -0
- data/lib/principles/models/other_principles.rb +47 -0
- data/lib/principles/models/placement.rb +42 -0
- data/lib/principles/models/plain_text_file.rb +32 -0
- data/lib/principles/models/principles_folder_move.rb +59 -0
- data/lib/principles/models/principles_package.rb +53 -0
- data/lib/principles/models/prior_finding_decision.rb +162 -0
- data/lib/principles/models/prior_finding_outcomes.rb +60 -0
- data/lib/principles/models/prior_findings.rb +109 -0
- data/lib/principles/models/problem_report.rb +110 -0
- data/lib/principles/models/project.rb +214 -0
- data/lib/principles/models/project_history.rb +203 -0
- data/lib/principles/models/record_judged_against.rb +42 -0
- data/lib/principles/models/recorded_entry.rb +93 -0
- data/lib/principles/models/recorded_finding.rb +97 -0
- data/lib/principles/models/recorded_findings.rb +58 -0
- data/lib/principles/models/recorded_members.rb +149 -0
- data/lib/principles/models/repoint.rb +112 -0
- data/lib/principles/models/reported_check.rb +130 -0
- data/lib/principles/models/request_id.rb +70 -0
- data/lib/principles/models/runs_from.rb +36 -0
- data/lib/principles/models/skill_loads.rb +123 -0
- data/lib/principles/models/still_holds_restatement.rb +87 -0
- data/lib/principles/models/text_size.rb +26 -0
- data/lib/principles/models/uncommitted_outside_the_map.rb +30 -0
- data/lib/principles/models/waits_on.rb +58 -0
- data/lib/principles/models/write_up_opening.rb +93 -0
- data/lib/principles/models/write_up_pointer.rb +50 -0
- data/lib/principles/models/write_up_repoint.rb +39 -0
- data/lib/principles/prose_list.rb +23 -0
- data/lib/principles/supported_ruby.rb +93 -0
- data/lib/principles/version.rb +11 -0
- data/lib/principles/views/acknowledged_since_last_audit.rb +34 -0
- data/lib/principles/views/acknowledgement_result.rb +44 -0
- data/lib/principles/views/activation_in_hand.rb +47 -0
- data/lib/principles/views/assessment_request.rb +131 -0
- data/lib/principles/views/audit_changes.rb +79 -0
- data/lib/principles/views/audit_commit.rb +55 -0
- data/lib/principles/views/audit_result.rb +147 -0
- data/lib/principles/views/candidate_files_result.rb +95 -0
- data/lib/principles/views/check_result_template.rb +75 -0
- data/lib/principles/views/diff_since_last_audit.rb +29 -0
- data/lib/principles/views/findings_result.rb +45 -0
- data/lib/principles/views/help.rb +119 -0
- data/lib/principles/views/invocation.rb +66 -0
- data/lib/principles/views/landed_since_base.rb +27 -0
- data/lib/principles/views/lines_shared_with_member.rb +111 -0
- data/lib/principles/views/loads_to_check_result.rb +45 -0
- data/lib/principles/views/map_result.rb +169 -0
- data/lib/principles/views/member_change_since_baseline.rb +58 -0
- data/lib/principles/views/member_repairs.rb +82 -0
- data/lib/principles/views/members_a_commit_changed.rb +66 -0
- data/lib/principles/views/members_with_no_baseline.rb +56 -0
- data/lib/principles/views/meta_principle_passage.rb +23 -0
- data/lib/principles/views/meta_principle_result.rb +170 -0
- data/lib/principles/views/open_findings_elsewhere_result.rb +32 -0
- data/lib/principles/views/open_findings_line.rb +125 -0
- data/lib/principles/views/other_principles_unjudged_result.rb +32 -0
- data/lib/principles/views/outside_the_map_result.rb +31 -0
- data/lib/principles/views/package_since_last_audit_result.rb +34 -0
- data/lib/principles/views/principle_read_result.rb +86 -0
- data/lib/principles/views/principle_with_changed_members.rb +26 -0
- data/lib/principles/views/principles_with_no_completed_audit.rb +30 -0
- data/lib/principles/views/printed_principle.rb +45 -0
- data/lib/principles/views/prior_finding_decisions_result.rb +35 -0
- data/lib/principles/views/prior_findings_result.rb +80 -0
- data/lib/principles/views/problem_lines.rb +84 -0
- data/lib/principles/views/problem_result.rb +107 -0
- data/lib/principles/views/prompts/acknowledge-a-change/say-whether-the-change-keeps-the-meaning.md +12 -0
- data/lib/principles/views/prompts/answer-any-judgement-request/what-a-self-audit-cannot-show.md +6 -0
- data/lib/principles/views/prompts/assess-every-member/answer-guide.json +63 -0
- data/lib/principles/views/prompts/assess-every-member/assess-each-member-and-the-set.md +12 -0
- data/lib/principles/views/prompts/assess-every-member/check-roles-other-principles-give-these-lines.md +7 -0
- data/lib/principles/views/prompts/assess-every-member/decide-each-member-loading.md +17 -0
- data/lib/principles/views/prompts/assess-every-member/decide-each-prior-finding.md +38 -0
- data/lib/principles/views/prompts/assess-every-member/give-problem-evidence-at-still-holds.md +2 -0
- data/lib/principles/views/prompts/assess-every-member/give-the-evidence-a-prior-finding-lacks.md +7 -0
- data/lib/principles/views/prompts/assess-every-member/have-a-changed-check-reviewed-before-running-it.md +4 -0
- data/lib/principles/views/prompts/assess-every-member/have-a-new-check-reviewed-before-running-it.md +3 -0
- data/lib/principles/views/prompts/assess-every-member/have-hidden-characters-in-a-check-explained.md +4 -0
- data/lib/principles/views/prompts/assess-every-member/judge-acknowledged-edits-together.md +6 -0
- data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-against-the-other-principles.md +7 -0
- data/lib/principles/views/prompts/assess-every-member/judge-the-keeping-under-the-release-since-the-last-audit.md +10 -0
- data/lib/principles/views/prompts/assess-every-member/judge-the-set-with-carried-assessments.md +10 -0
- data/lib/principles/views/prompts/assess-every-member/keep-the-last-review-basis-unless-you-give-a-new-one.md +1 -0
- data/lib/principles/views/prompts/assess-every-member/name-another-principles-open-finding-rather-than-raise-it.md +9 -0
- data/lib/principles/views/prompts/assess-every-member/read-whether-each-prior-finding-s-evidence-changed.md +12 -0
- data/lib/principles/views/prompts/assess-every-member/run-a-named-check-at-every-full-audit.md +2 -0
- data/lib/principles/views/prompts/assess-every-member/run-the-check-then-report-it.md +2 -0
- data/lib/principles/views/prompts/assess-every-member/run-the-reviewed-check-and-say-what-its-review-rested-on.md +6 -0
- data/lib/principles/views/prompts/assess-every-member/uncommitted-files-outside-the-map.md +4 -0
- data/lib/principles/views/prompts/assess-every-member/why-each-member-is-judged-again.md +24 -0
- data/lib/principles/views/prompts/dispose-of-each-finding/answer-guide.json +34 -0
- data/lib/principles/views/prompts/dispose-of-each-finding/fix-or-record-each-finding.md +24 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/ask-the-owner-before-mapping.md +1 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/choose-one-principle.md +5 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored-folders.md +2 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-ignored.md +3 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-possibly-test-data.md +2 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/owner-ask-untracked.md +2 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/what-each-command-writes.md +4 -0
- data/lib/principles/views/prompts/find-a-principle-to-map/where-the-inventory-looks.md +10 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/add-member.md +45 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-activation.md +21 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-description.md +2 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-member-check.md +17 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/change-member-role.md +9 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/create-principle.md +6 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/every-command-and-how-they-chain.md +28 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-candidate-files.md +17 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-member-problems.md +73 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-open-findings.md +71 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/list-principles.md +7 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/move-member.md +11 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/remove-member-check.md +10 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/remove-member.md +6 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-answer-check.md +16 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-audit.md +20 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-meta-principle-passage.md +52 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/show-principle.md +86 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/start-audit.md +68 -0
- data/lib/principles/views/prompts/learn-what-a-command-does/submit-answer.md +56 -0
- data/lib/principles/views/prompts/repoint-write-ups/point-each-moved-write-up-at-its-new-place.md +10 -0
- data/lib/principles/views/prompts/report-after-a-commit/it-refused-after-the-commits-touched-the-maps.md +2 -0
- data/lib/principles/views/prompts/report-after-a-commit/others-are-still-listed.md +1 -0
- data/lib/principles/views/prompts/report-after-a-commit/the-commits-changed-these-members.md +2 -0
- data/lib/principles/views/prompts/report-after-a-commit/this-command-shows-each-one.md +1 -0
- data/lib/principles/views/prompts/report-the-completed-audit/commit-only-what-the-audit-changed.md +2 -0
- data/lib/principles/views/prompts/report-the-completed-audit/replace-the-default-commit-message.md +4 -0
- data/lib/principles/views/prompts/report-the-completed-audit/what-completion-means.md +2 -0
- data/lib/principles/views/prompts/set-principles-up/entry-file-passage.md +3 -0
- data/lib/principles/views/prompts/set-principles-up/name-the-step-each-member-lacks.md +15 -0
- data/lib/principles/views/prompts/set-principles-up/place-the-passage-with-the-user.md +31 -0
- data/lib/principles/views/prompts/set-principles-up/set-up-first.md +2 -0
- data/lib/principles/views/prompts/set-principles-up/what-the-passage-presumes.md +4 -0
- data/lib/principles/views/prompts/start-a-session/act-on-the-open-findings.md +5 -0
- data/lib/principles/views/prompts/start-a-session/it-returns-after-each-clear-or-compaction-too.md +1 -0
- data/lib/principles/views/prompts/start-a-session/it-returns-at-each-session-start.md +1 -0
- data/lib/principles/views/prompts/start-a-session/raise-them-at-the-next-pause.md +1 -0
- data/lib/principles/views/prompts/start-a-session/raise-them-before-the-first-task.md +1 -0
- data/lib/principles/views/prompts.rb +71 -0
- data/lib/principles/views/repoint_result.rb +80 -0
- data/lib/principles/views/request_part.rb +26 -0
- data/lib/principles/views/request_parts.rb +26 -0
- data/lib/principles/views/shell_word.rb +29 -0
- data/lib/principles/views/visible_text.rb +26 -0
- data/lib/principles/views/workflow_diagram.rb +77 -0
- data/lib/principles.rb +5 -0
- metadata +326 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "audited_member_lines"
|
|
6
|
+
require_relative "evidence_item"
|
|
7
|
+
require_relative "evidence_replacements"
|
|
8
|
+
require_relative "recorded_finding"
|
|
9
|
+
require_relative "recorded_members"
|
|
10
|
+
|
|
11
|
+
module Principles
|
|
12
|
+
# The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
|
|
13
|
+
module Models
|
|
14
|
+
# One finding an audit record left open, deferred or accepted, with the principle and the time of that audit:
|
|
15
|
+
# what list-open-findings prints of it, and what the next full audit is asked to decide on it (D35).
|
|
16
|
+
# PriorFindings is the next audit's view of all of them, and checks its decisions. It carries the evidence it
|
|
17
|
+
# was raised with, which list-open-findings and the next full audit's request print, and which that audit keeps
|
|
18
|
+
# when it keeps the finding (D64, #839).
|
|
19
|
+
class OpenFinding
|
|
20
|
+
# members are the members as the audit that left this finding open recorded them, against which the next
|
|
21
|
+
# audit says whether each changed since.
|
|
22
|
+
#: (RecordedFinding, principle_id: String, audited_at: String?, members: RecordedMembers) -> void
|
|
23
|
+
def initialize(finding, principle_id:, audited_at:, members:)
|
|
24
|
+
@finding = finding
|
|
25
|
+
@principle_id = principle_id
|
|
26
|
+
@audited_at = audited_at
|
|
27
|
+
@members = members
|
|
28
|
+
freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The finding as list-open-findings prints it, with its principle, when its audit was made and when it was
|
|
32
|
+
# first raised, so how long it has waited shows (#629), what it says it waits on, since when (D52), and the
|
|
33
|
+
# evidence its record keeps, or that it keeps none (D64, #839).
|
|
34
|
+
#: () -> Hash[String, untyped]
|
|
35
|
+
def listed
|
|
36
|
+
{ "principle" => @principle_id, "finding_id" => id, "member_ids" => @finding.member_ids, **problem,
|
|
37
|
+
"action" => @finding.action, "recorded_at" => recorded_at&.to_h, "audited_at" => @audited_at,
|
|
38
|
+
"first_raised_at" => first_raised_at, **raised_in, **waiting, **kept_evidence }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The finding as the next full audit's request lists it, with when it was first raised, and what it waits on,
|
|
42
|
+
# since when, for the agent to confirm (D52).
|
|
43
|
+
#: () -> Hash[String, untyped]
|
|
44
|
+
def for_next_audit
|
|
45
|
+
{ "finding_id" => id, "member_ids" => @finding.member_ids, **problem,
|
|
46
|
+
**present("recorded_at", recorded_at&.to_h),
|
|
47
|
+
"action" => @finding.action,
|
|
48
|
+
"first_raised_at" => first_raised_at, **raised_in, **waiting }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The finding as the next full audit's request lists it, against the map's members now and each one's hash
|
|
52
|
+
# now, hashes_now: under its members' ids now, with whether each changed since the audit that left it open,
|
|
53
|
+
# what its record did not keep of it, and the evidence it keeps, so the agent deciding it sees what it rests on
|
|
54
|
+
# (#839). Given since, each evidence item also carries what since says of it now (#858, #877).
|
|
55
|
+
#: (MapMembers, Hash[String, String]) ?{ (EvidenceItem) -> Hash[String, bool] }
|
|
56
|
+
#| -> Hash[String, untyped]
|
|
57
|
+
def for_next_audit_against(members_now, hashes_now, &)
|
|
58
|
+
finding = for_next_audit
|
|
59
|
+
ids = ids_now(members_now)
|
|
60
|
+
since = ids.to_h { |id| [id, since_this_audit(members_now, hashes_now, id)] }
|
|
61
|
+
finding.merge("member_ids" => ids, "members_since" => since, **text_not_kept,
|
|
62
|
+
**kept_evidence(evidence_since(&)))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The finding as the next full audit's record keeps it, before that audit's decision on it: under its members'
|
|
66
|
+
# ids now, with its write-up, when it was first raised and the commit an earlier first_raised_at cited (D53).
|
|
67
|
+
#: (MapMembers) -> Hash[String, untyped]
|
|
68
|
+
def kept_against(members_now)
|
|
69
|
+
{ "id" => id, "member_ids" => ids_now(members_now), **problem, "action" => @finding.action,
|
|
70
|
+
**present("recorded_at", recorded_at&.to_h), "first_raised_at" => first_raised_at, **raised_in }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The principle whose record left it open.
|
|
74
|
+
#: String
|
|
75
|
+
attr_reader :principle_id
|
|
76
|
+
|
|
77
|
+
# The finding's id in its record.
|
|
78
|
+
#: () -> String
|
|
79
|
+
def id = @finding.id
|
|
80
|
+
|
|
81
|
+
# Where its write-up is, or nil when it was never written up.
|
|
82
|
+
#: () -> WriteUpPointer?
|
|
83
|
+
def recorded_at = @finding.recorded_at
|
|
84
|
+
|
|
85
|
+
# What its audit did about it: deferred or accepted.
|
|
86
|
+
#: () -> String?
|
|
87
|
+
def action = @finding.action
|
|
88
|
+
|
|
89
|
+
# Whether its record keeps what it says is wrong; one that keeps none is marked text_not_kept for the next full
|
|
90
|
+
# audit to supply.
|
|
91
|
+
#: () -> bool
|
|
92
|
+
def states_problem? = !@finding.problem.nil?
|
|
93
|
+
|
|
94
|
+
# The locators of the evidence it was raised with, or nil when its record keeps none (D64).
|
|
95
|
+
#: () -> Array[Hash[String, untyped]]?
|
|
96
|
+
def evidence = @finding.evidence
|
|
97
|
+
|
|
98
|
+
# Whether its record keeps the evidence it was raised with (D64).
|
|
99
|
+
#: () -> bool
|
|
100
|
+
def evidence_kept? = @finding.evidence_kept?
|
|
101
|
+
|
|
102
|
+
# The ids now, as the next full audit's request lists them, of its members no longer in the map, which its
|
|
103
|
+
# members_since calls removed.
|
|
104
|
+
#: (MapMembers) -> Array[String]
|
|
105
|
+
def removed_from(members_now) = ids_now(members_now).reject { |member_id| members_now.with_id(member_id) }
|
|
106
|
+
|
|
107
|
+
# The new places a decision's problem_evidence gives the evidence its record keeps, so the check that an item is
|
|
108
|
+
# no longer found and the swap read the same items (D70); none when its record keeps none, which problem_evidence
|
|
109
|
+
# gives instead (D64).
|
|
110
|
+
#: (Array[Hash[String, untyped]]?) -> EvidenceReplacements
|
|
111
|
+
def replacements(problem_evidence)
|
|
112
|
+
kept = evidence or return EvidenceReplacements.new([], [])
|
|
113
|
+
|
|
114
|
+
EvidenceReplacements.new(kept, problem_evidence || [])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The evidence its next record keeps after a decision gives problem_evidence: the evidence it was raised with,
|
|
118
|
+
# each item the decision gave a new place swapped for it (D70), or, when its record keeps none, the evidence the
|
|
119
|
+
# decision gave, kept as plain evidence since the record does not care which audit gave it (D64).
|
|
120
|
+
#: (Array[Hash[String, untyped]]?) -> Array[Hash[String, untyped]]?
|
|
121
|
+
def evidence_after(problem_evidence) = evidence ? replacements(problem_evidence).evidence : problem_evidence
|
|
122
|
+
|
|
123
|
+
# Whether it names the member known by key, under whichever id its audit recorded that member, so a member
|
|
124
|
+
# renamed since is still found (#772).
|
|
125
|
+
#: (String) -> bool
|
|
126
|
+
def on?(key) = @finding.member_ids.any? { |member_id| @members.key_of(member_id) == key }
|
|
127
|
+
|
|
128
|
+
# The finding as another principle's full audit lists it, once, under open_findings_elsewhere, so the same
|
|
129
|
+
# problem is not raised again there (#772, D62), with each item of its evidence and what its lines are beside
|
|
130
|
+
# lines, that audit's members, where locate finds the item now, or that its record keeps none (D64).
|
|
131
|
+
#: (AuditedMemberLines) { (Locator) -> LocatedText } -> Hash[String, untyped]
|
|
132
|
+
def listed_elsewhere(lines, &)
|
|
133
|
+
located = items&.map { |item| lines.facts_of(item, &) }
|
|
134
|
+
{ "principle_id" => @principle_id, "finding_id" => id, **problem, **present("action", @finding.action),
|
|
135
|
+
**kept_evidence(located) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# When the finding was first raised, which orders the findings by how long each has waited.
|
|
139
|
+
#: () -> Time
|
|
140
|
+
def first_raised_time = Time.iso8601(first_raised_at.to_s)
|
|
141
|
+
|
|
142
|
+
# The finding as the session-start line names it: its principle and id, the whole days it has waited since it
|
|
143
|
+
# was first raised until now, and what it says it waits on, when it says (D47, D52).
|
|
144
|
+
#: (Time) -> Hash[String, untyped]
|
|
145
|
+
def waited_at(now)
|
|
146
|
+
{ "principle" => @principle_id, "finding_id" => id,
|
|
147
|
+
"days_open" => ((now - first_raised_time) / 86_400).floor, **present("waits_on", @finding.waits_on) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Whether it records no waits_on, so the session-start line can count those whose line it cannot show (#736).
|
|
151
|
+
#: () -> bool
|
|
152
|
+
def without_waits_on? = !@finding.waits_on
|
|
153
|
+
|
|
154
|
+
# The one line it says it waits on, or nil when it says it waits on nothing (D52).
|
|
155
|
+
#: () -> String?
|
|
156
|
+
def waits_on = @finding.waits_on
|
|
157
|
+
|
|
158
|
+
# What the finding says it waits on, and since when: its own waits_on_since when an earlier audit asked it, or
|
|
159
|
+
# else this audit's time; nothing when it says it waits on nothing (D52).
|
|
160
|
+
#: () -> Hash[String, untyped]
|
|
161
|
+
def waiting
|
|
162
|
+
line = @finding.waits_on or return {}
|
|
163
|
+
{ "waits_on" => line, "waits_on_since" => @finding.waits_on_since || @audited_at }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Its own first_raised_at when an earlier audit kept it, or else this audit's time.
|
|
167
|
+
#: () -> String?
|
|
168
|
+
def first_raised_at = @finding.first_raised_at || @audited_at
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
# Its members' ids now, each member found by key so a member renamed since keeps its new id (#772).
|
|
173
|
+
#: (MapMembers) -> Array[String]
|
|
174
|
+
def ids_now(members_now) = @finding.member_ids.map { |id| id_now(members_now, @members.key_of(id)) || id }
|
|
175
|
+
|
|
176
|
+
#: (MapMembers, String) -> String?
|
|
177
|
+
def id_now(members_now, key) = members_now.with_key(key)&.id
|
|
178
|
+
|
|
179
|
+
#: (MapMembers, Hash[String, String], String) -> String
|
|
180
|
+
def since_this_audit(members_now, hashes_now, id)
|
|
181
|
+
member = members_now.with_id(id) or return "removed"
|
|
182
|
+
@members.freshness(member.key, hashes_now.fetch(id))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The commit whose record an audit cited for an earlier first_raised_at, when one did (D53).
|
|
186
|
+
#: () -> Hash[String, untyped]
|
|
187
|
+
def raised_in = present("raised_in", @finding.raised_in)
|
|
188
|
+
|
|
189
|
+
# Its evidence items, each with what since says of it now, or as kept without it (#858, #877).
|
|
190
|
+
#: () ?{ (EvidenceItem) -> Hash[String, bool] } -> Array[Hash[String, untyped]]?
|
|
191
|
+
def evidence_since(&since)
|
|
192
|
+
return @finding.evidence unless since
|
|
193
|
+
|
|
194
|
+
items&.map { |item| item.with_facts(since.call(item)) }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The evidence it was raised with, as items, or nil when its record keeps none (D64).
|
|
198
|
+
#: () -> Array[EvidenceItem]?
|
|
199
|
+
def items = @finding.evidence&.map { |fields| EvidenceItem.new(fields) }
|
|
200
|
+
|
|
201
|
+
# A mark when its record keeps no problem statement, for the next full audit to supply.
|
|
202
|
+
#: () -> Hash[String, untyped]
|
|
203
|
+
def text_not_kept = states_problem? ? {} : { "text_not_kept" => true }
|
|
204
|
+
|
|
205
|
+
# The evidence its record keeps, as items, which another principle's audit gives with each item's lines, or the
|
|
206
|
+
# mark that it keeps none, as a record written before this tool kept evidence does (D64, #839).
|
|
207
|
+
#: (?Array[Hash[String, untyped]]?) -> Hash[String, untyped]
|
|
208
|
+
def kept_evidence(listed = @finding.evidence)
|
|
209
|
+
evidence_kept? ? { "evidence" => listed } : { "evidence_not_kept" => true }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# What it says is wrong, under its key, or nothing when the record kept no text for it.
|
|
213
|
+
#: () -> Hash[String, untyped]
|
|
214
|
+
def problem = present("problem", @finding.problem)
|
|
215
|
+
|
|
216
|
+
# value under name, or nothing when there is none, as the record leaves out a field it has no value for.
|
|
217
|
+
#: (String, untyped) -> Hash[String, untyped]
|
|
218
|
+
def present(name, value) = value.nil? ? {} : { name => value }
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../error"
|
|
5
|
+
require_relative "members_elsewhere"
|
|
6
|
+
require_relative "open_finding"
|
|
7
|
+
|
|
8
|
+
module Principles
|
|
9
|
+
# The domain logic, which loads and saves itself through data/. models/CLAUDE.md owns the boundary.
|
|
10
|
+
module Models
|
|
11
|
+
# The findings other principles' records leave open on members sharing lines with a full audit's members, read
|
|
12
|
+
# once per principle: each member's mapped_elsewhere names them by id, and the request lists each once, since a
|
|
13
|
+
# whole file's findings printed in full beside every passage within it (#772, #786, D62).
|
|
14
|
+
class OpenFindingsElsewhere
|
|
15
|
+
# sharing gives, under each audited member's id, the other principles' members sharing its lines.
|
|
16
|
+
#: (Hash[String, MembersElsewhere], AuditRecords) -> OpenFindingsElsewhere
|
|
17
|
+
def self.of(sharing, records)
|
|
18
|
+
ids = sharing.values.flat_map(&:principle_ids).uniq
|
|
19
|
+
new(sharing, ids.to_h { |id| [id, records.left_open_or_none(id)] })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#: (Hash[String, MembersElsewhere], Hash[String, Array[OpenFinding]]) -> void
|
|
23
|
+
def initialize(sharing, left_open_by_principle)
|
|
24
|
+
@sharing = sharing
|
|
25
|
+
@left_open_by_principle = left_open_by_principle
|
|
26
|
+
freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The other principles' members sharing the lines of the audited member member_id, each naming by id the
|
|
30
|
+
# findings left open on it.
|
|
31
|
+
#: (String) -> Array[Hash[String, untyped]]
|
|
32
|
+
def beside(member_id) = @sharing.fetch(member_id).naming(@left_open_by_principle)
|
|
33
|
+
|
|
34
|
+
# Each finding left open on any of them, once, with its principle, and what its evidence's lines are beside
|
|
35
|
+
# lines, this audit's members, where locate finds each item now (D64): a finding is the same one when its
|
|
36
|
+
# principle and id are.
|
|
37
|
+
#: (AuditedMemberLines) { (Locator) -> LocatedText } -> Array[Hash[String, untyped]]
|
|
38
|
+
def listed(lines, &)
|
|
39
|
+
@sharing.values.flat_map { |members| members.open_among(@left_open_by_principle) }
|
|
40
|
+
.uniq { |finding| [finding.principle_id, finding.id] }
|
|
41
|
+
.map { |finding| finding.listed_elsewhere(lines, &) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "member_problems"
|
|
5
|
+
require_relative "member_state"
|
|
6
|
+
|
|
7
|
+
module Principles
|
|
8
|
+
# The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
|
|
9
|
+
module Models
|
|
10
|
+
# Every principle of the project but one, each as list-member-problems reports its members, which an audit of
|
|
11
|
+
# that one names when their members need an audit of their own: a completed audit for those its fixes changed
|
|
12
|
+
# (#410, #823), and the meta-principle's full audit for those no audit has judged as they are now (D71).
|
|
13
|
+
class OtherPrinciples
|
|
14
|
+
# Each principle of project but the one id names, its members as MemberProblems gives them; a map this tool
|
|
15
|
+
# cannot read is among them, as list-member-problems lists it.
|
|
16
|
+
#: (Project, but: String) -> OtherPrinciples
|
|
17
|
+
def self.of(project, but:)
|
|
18
|
+
new((project.member_maps.ids - [but]).to_h { [it, MemberProblems.for(project, it, listing_all: true)] })
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# problems maps each principle's id to its members' problems.
|
|
22
|
+
#: (Hash[String, MemberProblems]) -> void
|
|
23
|
+
def initialize(problems)
|
|
24
|
+
@problems = problems.freeze #: Hash[String, MemberProblems]
|
|
25
|
+
freeze
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Each principle with a member in paths whose own text changed or whose reference broke, by id, with those
|
|
29
|
+
# members; a principle with none is left out.
|
|
30
|
+
#: (Array[String]) -> Hash[String, Array[MemberState]]
|
|
31
|
+
def changed_or_broken_in(paths) = listed { it.changed_or_broken_in(paths) }
|
|
32
|
+
|
|
33
|
+
# Each principle with members no audit has judged as they are now, by id, with those members by id and state;
|
|
34
|
+
# a principle with none is left out.
|
|
35
|
+
#: () -> Hash[String, Array[Hash[String, untyped]]]
|
|
36
|
+
def unjudged = listed(&:unjudged)
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# What the block gives for each principle's problems, by id, where it gives any.
|
|
41
|
+
#: [T] () { (MemberProblems) -> Array[T] } -> Hash[String, Array[T]]
|
|
42
|
+
def listed(&)
|
|
43
|
+
@problems.transform_values(&).reject { |_, members| members.empty? }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Principles
|
|
5
|
+
# The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
|
|
6
|
+
module Models
|
|
7
|
+
# Where a member may be read at a commit, and the hash of its text there, nil where it cannot be found. A
|
|
8
|
+
# one-member audit takes as the member's baseline the oldest placement that holds the text the audit record
|
|
9
|
+
# vouches for (#550).
|
|
10
|
+
class Placement
|
|
11
|
+
#: String
|
|
12
|
+
attr_reader :commit
|
|
13
|
+
|
|
14
|
+
#: Hash[String, untyped]
|
|
15
|
+
attr_reader :reference
|
|
16
|
+
|
|
17
|
+
#: String?
|
|
18
|
+
attr_reader :sha256
|
|
19
|
+
|
|
20
|
+
# Where the member is at commit, as the Locator MemberFile reads.
|
|
21
|
+
#: () -> Locator
|
|
22
|
+
def locator = Locator.new(reference)
|
|
23
|
+
|
|
24
|
+
#: (commit: String, reference: Hash[String, untyped], sha256: String?) -> void
|
|
25
|
+
def initialize(commit:, reference:, sha256:)
|
|
26
|
+
@commit = commit
|
|
27
|
+
@reference = reference
|
|
28
|
+
@sha256 = sha256
|
|
29
|
+
freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether the member's text here is the text whose hash is recorded.
|
|
33
|
+
#: (String?) -> bool
|
|
34
|
+
def holds?(recorded) = !sha256.nil? && sha256 == recorded
|
|
35
|
+
|
|
36
|
+
# The paths a change from here to the member at now_path spans, this one first, so a diff over them follows
|
|
37
|
+
# a move (#551).
|
|
38
|
+
#: (String) -> Array[String]
|
|
39
|
+
def paths_to(now_path) = [reference.fetch("path"), now_path].uniq
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "member_file"
|
|
5
|
+
|
|
6
|
+
module Principles
|
|
7
|
+
# The domain logic, which loads and saves itself through data/. models/CLAUDE.md owns the boundary.
|
|
8
|
+
module Models
|
|
9
|
+
# A file that is not Markdown, such as a script or a config file. It has no sections, so a member is the
|
|
10
|
+
# whole file or a passage of it, and a # line is never read as a heading.
|
|
11
|
+
class PlainTextFile < MemberFile
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
# The whole text; a heading is refused, since a # line here is a comment, not a heading.
|
|
15
|
+
#: (Array[String], Locator) -> [Integer, Integer]
|
|
16
|
+
def region(lines, locator)
|
|
17
|
+
if locator.heading
|
|
18
|
+
raise Error.new("section_needs_markdown", "#{@path} is not Markdown, so it has no sections",
|
|
19
|
+
"Name the member by a passage with --starting-at TEXT [--ending-at TEXT], or map the " \
|
|
20
|
+
"whole file.", @path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if locator.query
|
|
24
|
+
raise Error.new("query_needs_frontmatter", "#{@path} is not Markdown, so it has no frontmatter to query",
|
|
25
|
+
"Name the member by a passage with --starting-at TEXT, or map the whole file.", @path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
[0, lines.length]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../error"
|
|
5
|
+
require_relative "../data/principles_subfolder"
|
|
6
|
+
|
|
7
|
+
module Principles
|
|
8
|
+
# The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
|
|
9
|
+
module Models
|
|
10
|
+
# Whether a project's maps and records have moved from .memeplex, the folder's earlier name, to .principles.
|
|
11
|
+
# Reading the new folder alone would show an unmoved project as unmapped, so every command that reads either
|
|
12
|
+
# refuses until the folder moves, or until one of two folders holding them is removed. A file an older version
|
|
13
|
+
# left in .memeplex beside no maps or records does not count (#536, rename brief findings 1 and 9).
|
|
14
|
+
class PrinciplesFolderMove
|
|
15
|
+
FOLDER = Data::PrinciplesSubfolder::FOLDER #: String
|
|
16
|
+
EARLIER = Data::PrinciplesSubfolder::EARLIER_FOLDER #: String
|
|
17
|
+
|
|
18
|
+
# held names the folders that hold maps/ or audits/.
|
|
19
|
+
#: (Array[String]) -> void
|
|
20
|
+
def initialize(held)
|
|
21
|
+
@held = held
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Refuses while the earlier folder holds maps or records.
|
|
26
|
+
#: () -> void
|
|
27
|
+
def refuse_while_pending
|
|
28
|
+
return unless @held.include?(EARLIER)
|
|
29
|
+
|
|
30
|
+
raise both_folders if @held.include?(FOLDER)
|
|
31
|
+
|
|
32
|
+
raise not_moved
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
#: () -> Error
|
|
38
|
+
def both_folders
|
|
39
|
+
Error.new("both_principles_folders",
|
|
40
|
+
"Both #{EARLIER} and #{FOLDER} hold maps or audit records; this version reads #{FOLDER} only",
|
|
41
|
+
"Removing either folder changes the user's project, so ask the user first. Compare the two " \
|
|
42
|
+
"before removing either: move into #{FOLDER} each map or record that only " \
|
|
43
|
+
"#{EARLIER} holds, and where both hold one, compare them and keep the current one; in git, " \
|
|
44
|
+
"git log shows which changed last. Then remove #{EARLIER} and commit.", EARLIER)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#: () -> Error
|
|
48
|
+
def not_moved
|
|
49
|
+
Error.new("principles_folder_not_moved",
|
|
50
|
+
"This project's maps or audit records are in #{EARLIER}, the folder's earlier name; this version " \
|
|
51
|
+
"reads #{FOLDER}",
|
|
52
|
+
"Moving the folder changes the user's project, so ask the user before moving it.\n" \
|
|
53
|
+
"To move it in git: git mv #{EARLIER} #{FOLDER}\nOutside git: mv #{EARLIER} #{FOLDER}\n" \
|
|
54
|
+
"Then commit the move. If anything untracked is left in #{EARLIER}, such as state.json and " \
|
|
55
|
+
"lock, which an older version wrote, ask the user before deleting it.", EARLIER)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "digest"
|
|
5
|
+
|
|
6
|
+
module Principles
|
|
7
|
+
# The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
|
|
8
|
+
module Models
|
|
9
|
+
# The release of principles an audit of the meta-principle ran under: its version, and a digest of the passage
|
|
10
|
+
# it proposes for setup, as printed. An upgrade can change the session-start hook, the skill and the passage,
|
|
11
|
+
# none of which a consuming project maps, so the meta-principle's record keeps this and its next audit states
|
|
12
|
+
# what changed since (D68, synthesis F3 and F12).
|
|
13
|
+
class PrinciplesPackage
|
|
14
|
+
# The key the passage's digest is kept under. The record keeps its digests by key, as releases before D71 kept
|
|
15
|
+
# one for each of two passage forms, so their records stay valid; neither form's key is this one, so a record
|
|
16
|
+
# one of them wrote reads as a passage change once, at the next audit (D71).
|
|
17
|
+
PASSAGE = "entry_file_passage"
|
|
18
|
+
|
|
19
|
+
# The release running now, from its version and the passage's text.
|
|
20
|
+
#: (version: String, passage: String) -> PrinciplesPackage
|
|
21
|
+
def self.of(version:, passage:)
|
|
22
|
+
new("version" => version, "passage_sha256" => { PASSAGE => Digest::SHA256.hexdigest(passage) })
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The release a record kept, or nil when it kept none, as a record of another principle or one an older
|
|
26
|
+
# version wrote does not.
|
|
27
|
+
#: (Hash[String, untyped]?) -> PrinciplesPackage?
|
|
28
|
+
def self.kept(fields) = fields ? new(fields) : nil
|
|
29
|
+
|
|
30
|
+
#: (Hash[String, untyped]) -> void
|
|
31
|
+
def initialize(fields)
|
|
32
|
+
@fields = fields.freeze #: Hash[String, untyped]
|
|
33
|
+
freeze
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#: () -> String
|
|
37
|
+
def version = @fields.fetch("version")
|
|
38
|
+
|
|
39
|
+
# Whether the passage's text differs from the one earlier proposed, or earlier kept no digest of it.
|
|
40
|
+
#: (PrinciplesPackage) -> bool
|
|
41
|
+
def passage_changed_since?(earlier) = earlier.passage_sha256 != passage_sha256
|
|
42
|
+
|
|
43
|
+
# As a record keeps it.
|
|
44
|
+
#: () -> Hash[String, untyped]
|
|
45
|
+
def to_h = @fields
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
|
|
49
|
+
#: () -> String?
|
|
50
|
+
def passage_sha256 = @fields.fetch("passage_sha256")[PASSAGE]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../error"
|
|
5
|
+
require_relative "answer_fields"
|
|
6
|
+
require_relative "answer_place"
|
|
7
|
+
require_relative "answer_text"
|
|
8
|
+
require_relative "first_raised_claim"
|
|
9
|
+
require_relative "kept_prior_finding"
|
|
10
|
+
require_relative "open_finding"
|
|
11
|
+
require_relative "still_holds_restatement"
|
|
12
|
+
require_relative "waits_on"
|
|
13
|
+
|
|
14
|
+
module Principles
|
|
15
|
+
# The domain logic, and the mediation of persistence. models/CLAUDE.md owns the boundary.
|
|
16
|
+
module Models
|
|
17
|
+
# One decision on a prior finding the request listed (D35), checked against that finding and this audit's
|
|
18
|
+
# new findings. Whether the finding still holds is the agent's judgement; this tool checks the decision's shape.
|
|
19
|
+
class PriorFindingDecision
|
|
20
|
+
KINDS = %w[still_holds resolved withdrawn raised_again].freeze
|
|
21
|
+
CLOSING = %w[resolved withdrawn].freeze
|
|
22
|
+
# What the completed result shows of each closed finding.
|
|
23
|
+
CLOSED_FIELDS = %w[finding_id decision reason evidence].freeze
|
|
24
|
+
REPAIR = "Decide it still_holds, raised_again with as, or resolved or withdrawn with a one-line reason."
|
|
25
|
+
|
|
26
|
+
# finding is the prior finding as its record left it open; at is the decision's place in the answer, such as
|
|
27
|
+
# /prior_finding_decisions/0, which a refusal about its pointers names; members are the map's members now, which
|
|
28
|
+
# a kept finding's members must still be among.
|
|
29
|
+
#: (Hash[String, untyped], OpenFinding, Array[String], at: String, members: MapMembers) -> void
|
|
30
|
+
def initialize(decision, finding, new_ids, at:, members:)
|
|
31
|
+
@decision = decision.reject { |key, value| key == "problem_evidence" && value == [] } #: Hash[String, untyped]
|
|
32
|
+
@finding = finding
|
|
33
|
+
@new_ids = new_ids
|
|
34
|
+
@members = members
|
|
35
|
+
@id = finding.id #: String
|
|
36
|
+
@place = AnswerPlace.new(at, "Prior finding #{@id}") #: AnswerPlace
|
|
37
|
+
freeze
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Refuses the decision unless its kind, reason, as, any evidence of a fix and any restated problem or pointer
|
|
41
|
+
# are sound. locate checks that each evidence pointer and a new recorded_at resolve, as a deferral's is checked.
|
|
42
|
+
#: (^(Locator) -> void) -> void
|
|
43
|
+
def check(locate)
|
|
44
|
+
kind = @decision["decision"]
|
|
45
|
+
invalid("#{kind.inspect} is not a decision") unless KINDS.include?(kind)
|
|
46
|
+
kind == "still_holds" ? holds : closed_or_raised(kind)
|
|
47
|
+
fix_shown(locate)
|
|
48
|
+
new_action
|
|
49
|
+
StillHoldsRestatement.new(@decision, @finding, @place, refuse: refusal).check(locate)
|
|
50
|
+
WaitsOn.new(@decision["waits_on"], action: kept_action).check(code: "invalid_decision",
|
|
51
|
+
subject: "Prior finding #{@id}")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The prior finding it decides, as its record left it open.
|
|
55
|
+
#: OpenFinding
|
|
56
|
+
attr_reader :finding
|
|
57
|
+
|
|
58
|
+
# What it decides: still_holds, resolved, withdrawn or raised_again.
|
|
59
|
+
#: () -> String
|
|
60
|
+
def kind = @decision.fetch("decision")
|
|
61
|
+
|
|
62
|
+
# The id of the new finding a raised_again decision raises it as.
|
|
63
|
+
#: () -> String
|
|
64
|
+
def raised_as = @decision.fetch("as")
|
|
65
|
+
|
|
66
|
+
# The decision as the completed result shows a closed finding.
|
|
67
|
+
#: () -> Hash[String, untyped]
|
|
68
|
+
def closed = @decision.slice(*CLOSED_FIELDS)
|
|
69
|
+
|
|
70
|
+
# The finding a still_holds decision keeps, against the map's members now. Its problem_evidence is read with an
|
|
71
|
+
# empty list as none given: the template starts a marked finding's decision with one, which an agent leaves in
|
|
72
|
+
# whatever it decides (#838).
|
|
73
|
+
#: () -> KeptPriorFinding
|
|
74
|
+
def kept = KeptPriorFinding.new(@decision, @finding, @members)
|
|
75
|
+
|
|
76
|
+
# The earlier first_raised_at this decision claims, if any, to check against the record it cites (D53).
|
|
77
|
+
#: () -> FirstRaisedClaim
|
|
78
|
+
def first_raised_claim = FirstRaisedClaim.new(@decision, @finding)
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# A still_holds decision may make its finding deferred or accepted, with a one-line reason, and a deferral
|
|
83
|
+
# says where it is written up, if only as null, as a correction's does (D35 brief, finding 14).
|
|
84
|
+
#: () -> void
|
|
85
|
+
def new_action
|
|
86
|
+
action = @decision["action"] or return
|
|
87
|
+
unless @decision["decision"] == "still_holds"
|
|
88
|
+
invalid("only still_holds may give action", "Drop action from #{@id}'s decision, or decide it still_holds.")
|
|
89
|
+
end
|
|
90
|
+
invalid("still_holds may make it only deferred or accepted") unless %w[deferred accepted].include?(action)
|
|
91
|
+
changed_action(action)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#: (String) -> void
|
|
95
|
+
def changed_action(action)
|
|
96
|
+
if action == @finding.action
|
|
97
|
+
invalid("it is already #{action}", "Drop action from #{@id}'s decision; it is already #{action}.")
|
|
98
|
+
end
|
|
99
|
+
invalid("a new action needs a one-line reason") unless one_line_reason?
|
|
100
|
+
unrecorded_deferral if action == "deferred" && !@decision.key?("recorded_at")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#: () -> bot
|
|
104
|
+
def unrecorded_deferral
|
|
105
|
+
invalid("deferring it needs recorded_at, or null when it is written up nowhere",
|
|
106
|
+
"Set recorded_at to where #{@id} is written up, as {\"path\": ..., \"heading\": ...}, or to null.")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The action the finding keeps once decided, or nil when the decision closes it or raises it again (D52).
|
|
110
|
+
#: () -> String?
|
|
111
|
+
def kept_action
|
|
112
|
+
return nil unless @decision["decision"] == "still_holds"
|
|
113
|
+
|
|
114
|
+
@decision["action"] || @finding.action
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
#: () -> void
|
|
118
|
+
def holds
|
|
119
|
+
gone unless @finding.removed_from(@members).empty?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
#: (String) -> void
|
|
123
|
+
def closed_or_raised(kind)
|
|
124
|
+
invalid("#{kind} needs a one-line reason") if CLOSING.include?(kind) && !one_line_reason?
|
|
125
|
+
invalid("raised_again needs as: the id of a new finding") if kind == "raised_again" && !raised_as_new?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#: () -> bool
|
|
129
|
+
def one_line_reason? = AnswerText.new(@decision["reason"]).one_line?
|
|
130
|
+
|
|
131
|
+
#: () -> bool
|
|
132
|
+
def raised_as_new? = @new_ids.include?(@decision["as"])
|
|
133
|
+
|
|
134
|
+
# A resolved decision may point at where its fix shows (D35 brief, finding 10).
|
|
135
|
+
#: (^(Locator) -> void) -> void
|
|
136
|
+
def fix_shown(locate)
|
|
137
|
+
return unless @decision.key?("evidence")
|
|
138
|
+
|
|
139
|
+
unless @decision["decision"] == "resolved"
|
|
140
|
+
invalid("only resolved may give evidence", "Drop evidence from #{@id}'s decision, or decide it resolved.")
|
|
141
|
+
end
|
|
142
|
+
AnswerFields.new(locate).evidence(@decision["evidence"], @place.child("evidence"))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The decision's refusal, for a part that checks it apart: a reason and a repair, or REPAIR for nil.
|
|
146
|
+
#: () -> ^(String, String?) -> void
|
|
147
|
+
def refusal = ->(why, repair) { invalid(why, repair || REPAIR) }
|
|
148
|
+
|
|
149
|
+
#: (String, ?String) -> bot
|
|
150
|
+
def invalid(why, repair = REPAIR)
|
|
151
|
+
raise Error.new("invalid_decision", "Prior finding #{@id}: #{why}", repair)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
#: () -> bot
|
|
155
|
+
def gone
|
|
156
|
+
removed = @finding.removed_from(@members)
|
|
157
|
+
raise Error.new("unknown_member", "Prior finding #{@id} names members no longer mapped: #{removed.join(", ")}",
|
|
158
|
+
"Decide it resolved or withdrawn with a reason, or raise it again as a new finding.")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|