Module: OKF::Pro::Records
- Defined in:
- lib/okf/pro/records.rb
Overview
The append-only record, checked against git rather than against a tool event.
Guards.journal_guard refuses an edit to a past day at the agent's
tool boundary, which is the right place to catch it and the wrong place
to rely on: it reads a PreToolUse event, so it sees Edit and Write and
nothing else. A shell redirect, an editor, a patch — none of them
produce the event, and the record they rewrite is exactly the artefact
nobody can reconstruct afterwards.
This asks the same question of the index, where the answer does not depend on which tool made the change: does this commit modify or delete a journal day that already exists in HEAD, and is that day in the past? Only additions and today's entry survive. It runs at the commit door, which every path to history goes through.
Constant Summary collapse
- JOURNAL_ENTRY =
%r{journal/(\d{4}-\d{2}-\d{2})\.md\z}.freeze
- VERBS =
What the status letter did to the day, in the words a refusal needs. A typechange is its own verb because it is its own act: the day's content is not edited, it is replaced wholesale by a link somewhere else.
{ "D" => "deletes", "T" => "replaces" }.freeze
Class Method Summary collapse
-
.staged_status(root) ⇒ Object
Modified, deleted, renamed and TYPECHANGED paths in the index, against HEAD.
-
.staged_violations(root, today: Date.today) ⇒ Object
Findings, or [].
Class Method Details
.staged_status(root) ⇒ Object
Modified, deleted, renamed and TYPECHANGED paths in the index, against HEAD. Additions are absent by construction — a day being written for the first time is the reconstruction the journal guard already routes to the owner, and this door does not second-guess an approval it cannot see.
T was absent and is the worst omission of the four: replacing a
committed past day with a symlink stages as a typechange, never as M
or D, so the gate reported "append-only" and the commit destroyed the
one artefact this module exists to protect. The filter is a whitelist,
so every letter it does not name fails OPEN — which is why it is named
here with its reason rather than left to be re-derived.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/okf/pro/records.rb', line 62 def staged_status(root) out = IO.popen( [ "git", "-C", root, "diff", "--cached", "--name-status", "--diff-filter=MDRT" ], err: File::NULL, &:read ) # A git that could not answer is not an empty answer. The commit door # refuses what it cannot check, exactly as dirty_markdown? does. unless $?.success? return { findings: [ " records git could not report the staged changes, so the " \ "append-only record could not be checked. The commit is refused " \ "rather than waved through." ], entries: [] } end entries = out.to_s.each_line.map do |line| mode, *paths = line.strip.split("\t") next if mode.nil? || paths.empty? # A rename reports source and destination; the source is the one # leaving its place. [ mode, paths.first ] end.compact { findings: [], entries: entries } rescue Errno::ENOENT { findings: [ " records git is not available, so the append-only record could not be " \ "checked. The commit is refused rather than waved through." ], entries: [] } end |
.staged_violations(root, today: Date.today) ⇒ Object
Findings, or []. root is the repository — this reads the index, not
a materialised tree, because the question is about a change rather
than a state.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/okf/pro/records.rb', line 28 def staged_violations(root, today: Date.today) status = staged_status(root) return status[:findings] unless status[:findings].empty? status[:entries].map do |mode, path| match = path.match(JOURNAL_ENTRY) next unless match day = match[1] next unless day < today.to_s verb = VERBS.fetch(mode[0, 1], "modifies") " records this commit #{verb} #{path}, a past day that already exists in HEAD. " \ "Records are append-only; corrections go in today's entry, pointing back." end.compact end |