Module: OKF::Pro::Audit
- Defined in:
- lib/okf/pro/audit.rb
Overview
The CI door: the same invariants the hooks enforce, minus the ones that need a tool event.
It exists because hooks fire only at the agent's tool boundary — an edit made in a text editor reaches the repo unchecked. The snapshot check asks whether the newest day in the log carries one, not today's: CI runs on a push, which can land on a day nobody worked, and a gate that fails on the calendar teaches people to ignore it.
Constant Summary collapse
- CORE =
The closed core.
okf validateknows nothing of these — they are the method's skeleton, not the format's — and a bundle missing one is not a leaner bundle, it is a broken one: without them the gates disengage in silence, which is the one failure the contract forbids. roadmap.md is deliberately absent from this list — it is the one file the design lets an adopter delete, by its own standing accusation. [ [ "board.md", "the one page of state" ], [ "log.md", "delta memory, where the snapshot lands" ], [ "journal/", "the backward record" ], [ "areas/corpus.md", "the standard this corpus is kept at" ] ].freeze
Class Method Summary collapse
-
.ambiguous_layout(start, root) ⇒ Object
Two bundles in one directory — a flat root that also parents an
.okfroot. -
.call(start) ⇒ Object
Takes a repository root or a bundle root; both resolve.
-
.confession(report) ⇒ Object
The guard on the guard.
-
.conformance(bundle) ⇒ Object
Errors AND warnings.
- .curation(bundle) ⇒ Object
-
.linted(bundle) ⇒ Object
The linter, run the way
Conformanceruns it, and for the same reason stated there at length: a bareLinter.call(bundle)cannot runexpired(no clock) orstale(no cutoff), and reportshealthy?anyway. - .pairing(root, board:, concepts:) ⇒ Object
- .snapshot(root) ⇒ Object
- .structure(root) ⇒ Object
Class Method Details
.ambiguous_layout(start, root) ⇒ Object
Two bundles in one directory — a flat root that also parents an
.okf root. The doors disagree there by construction: the trust
guards govern a file outside .okf by the flat root (a root that
does not contain the file is no root of it), while this audit and
the stop gate govern .okf. Neither choice is wrong; the LAYOUT
is, and a disagreement nobody is told about is the silent failure
the contract forbids. Mid-migration is the honest case, and the
answer is the same: finish the move.
53 54 55 56 57 58 59 60 61 |
# File 'lib/okf/pro/audit.rb', line 53 def ambiguous_layout(start, root) base = File.(start.to_s) return [] unless root == File.join(base, BundleRoot::DIR) return [] if BundleRoot.root_kind(base).nil? [ "#{base} is a bundle AND holds a bundle at #{BundleRoot::DIR}/ — two roots, one " \ "directory. Files outside #{BundleRoot::DIR}/ are guarded against the outer one and " \ "audited against the inner: the doors disagree until one of the two is retired." ] end |
.call(start) ⇒ Object
Takes a repository root or a bundle root; both resolve. CI runs it as
okf pro audit . from the top of the checkout, which is neither
more nor less correct than naming .okf — the caller should not have
to know the layout to ask whether the bundle is sound.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/okf/pro/audit.rb', line 20 def call(start) root = BundleRoot.resolve(start) if root.nil? return [ "#{File.(start.to_s)} holds no OKF bundle — no index.md, and none in .okf/." ] end bundle = ::OKF::Bundle::Reader.read(root) findings = ambiguous_layout(start, root).map { |m| " layout #{m}" } + structure(root).map { |m| " structure #{m}" } + conformance(bundle) + curation(bundle) + snapshot(root) # Budget and pairing read the board; with the board gone the structure # finding is the whole story, and a crash would bury it. The board is # read once and the bundle parsed once, here — Rule 3 goes through # Budget.check_text (the same code the cap-check hook runs, so the # doors cannot drift apart), and pairing reuses both reads. board_path = File.join(root, "board.md") if File.exist?(board_path) board = Pro.read_text(board_path) findings += Budget.check_text(board).map { |m| " budget #{m}" } findings += Board.grammar(board).map { |m| " board #{m}" } findings += pairing(root, board: board, concepts: bundle.concepts) end findings end |
.confession(report) ⇒ Object
The guard on the guard. stale is excluded in source with its reason,
so this is empty in normal operation — and stays a live guard for the
day okf adds a third clock-gated check, which would otherwise go
unchecked here in silence.
129 130 131 132 133 134 135 |
# File 'lib/okf/pro/audit.rb', line 129 def confession(report) skipped = Array(report.stats[:skipped_checks]) return [] if skipped.empty? [ " lint okf lint could not run #{skipped.join(", ")} on this bundle, so those are " \ "unchecked rather than clean." ] end |
.conformance(bundle) ⇒ Object
Errors AND warnings. Returning early on valid? dropped every soft
finding the validator made — and Conformance, the hook door, reports
them (warn_lines) for a reason it states at length: a scalar
verified: human:rod is conformant, so the attestation guard asks, the
owner approves, and the reader then drops the malformed value while the
trust tier stays unverified forever. Caught at the agent's tool
boundary and waved through here, which is the door an edit made in an
editor takes, and the door pre-commit runs.
A warning is a finding at this door, the same as a linter warning
already is: curation below reads .warnings and this verb exits 1 on
them. The two channels answering differently about the same bundle was
the inconsistency, not the severity.
99 100 101 102 103 |
# File 'lib/okf/pro/audit.rb', line 99 def conformance(bundle) result = ::OKF::Bundle::Validator.call(bundle) result.errors.map { |e| " validate #{e[:path]}: #{e[:message]}" } + result.warnings.map { |w| " validate #{w[:path]}: #{w[:message]} (#{w[:check]})" } end |
.curation(bundle) ⇒ Object
119 120 121 122 123 |
# File 'lib/okf/pro/audit.rb', line 119 def curation(bundle) report = linted(bundle) report.warnings.map { |w| " lint #{w[:path]}: #{w[:message]} (#{w[:check]})" } + confession(report) end |
.linted(bundle) ⇒ Object
The linter, run the way Conformance runs it, and for the same reason
stated there at length: a bare Linter.call(bundle) cannot run
expired (no clock) or stale (no cutoff), and reports healthy?
anyway. Measured on this door before this line existed:
skipped_checks: [:expired, :stale], and okf pro audit — clean. The
hook door supplied the clock and excluded stale in source; this one
did neither, so one clause was kept at one door and broken at the other.
Separate from curation so the residue can be asserted directly rather
than inferred from the findings — the check that this check ran.
115 116 117 |
# File 'lib/okf/pro/audit.rb', line 115 def linted(bundle) ::OKF::Bundle::Linter.call(bundle, today: Date.today, except: [ :stale ]) end |
.pairing(root, board:, concepts:) ⇒ Object
155 156 157 158 |
# File 'lib/okf/pro/audit.rb', line 155 def pairing(root, board:, concepts:) Pairing.failures(root, board: board, concepts: concepts) .map { |m| " pairing #{m.sub(/\A— pairing:/, "")}" } end |
.snapshot(root) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/okf/pro/audit.rb', line 137 def snapshot(root) path = File.join(root, "log.md") return [] unless File.exist?(path) text = Pro.read_text(path) # A heading the calendar rejects is a day no check will ever look # under — said here, because days() skipping it in silence is how a # one-digit typo would orphan an entry forever. findings = Log.malformed_days(text).map do |day| " snapshot log.md heading '## #{day}' is not a calendar date — nothing will ever check under it." end newest = Log.newest_day(text) unless newest.nil? || Log.snapshot_under?(text, newest) findings << " snapshot log.md's newest day (#{newest}) carries no Snapshot line." end findings end |
.structure(root) ⇒ Object
76 77 78 79 80 81 82 83 84 |
# File 'lib/okf/pro/audit.rb', line 76 def structure(root) CORE.map do |rel, why| path = File.join(root, rel) present = rel.end_with?("/") ? File.directory?(path) : File.file?(path) next if present "#{rel} is missing — #{why}. The core is closed; only roadmap.md is deletable." end.compact end |