-
Notifications
You must be signed in to change notification settings - Fork 1
feat(hygiene): structural prevention for tick-history row-ordering bug — last-row-is-latest CI check + append helper #532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,81 @@ | ||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||
| # | ||||||||||||||||||
| # tools/hygiene/append-tick-history-row.sh — appends a row to | ||||||||||||||||||
| # docs/hygiene-history/loop-tick-history.md using bash heredoc | ||||||||||||||||||
| # (which naturally produces chronological tail-append). | ||||||||||||||||||
| # | ||||||||||||||||||
| # Why this exists (Aaron 2026-04-26): | ||||||||||||||||||
| # The Edit-tool default with old_string=existing-line tends to | ||||||||||||||||||
| # insert NEW content BEFORE the matched line, producing | ||||||||||||||||||
| # reverse-chronological order. This script wraps the correct | ||||||||||||||||||
| # pattern (`cat >> file`) so the bug shape can't occur via this | ||||||||||||||||||
| # entrypoint. | ||||||||||||||||||
| # | ||||||||||||||||||
| # Usage: | ||||||||||||||||||
| # tools/hygiene/append-tick-history-row.sh "FULL_ROW_TEXT" | ||||||||||||||||||
| # | ||||||||||||||||||
| # The argument is the entire row including leading `| ` and | ||||||||||||||||||
| # trailing `|`. Caller is responsible for row content; this | ||||||||||||||||||
| # script is dumb-pipe. | ||||||||||||||||||
| # | ||||||||||||||||||
| # What this validates: | ||||||||||||||||||
| # - Argument starts with `| YYYY-MM-DDTHH:MM:SSZ (` | ||||||||||||||||||
| # - The timestamp is >= the latest existing row timestamp | ||||||||||||||||||
| # (otherwise reject — chronological discipline) | ||||||||||||||||||
| # | ||||||||||||||||||
| # What this does NOT do: | ||||||||||||||||||
| # - Does NOT format the row for you. The caller decides | ||||||||||||||||||
| # content (this is signal-in-signal-out per | ||||||||||||||||||
| # memory/feedback_signal_in_signal_out_clean_or_better_dsp_discipline.md) | ||||||||||||||||||
| # - Does NOT commit. The caller stages + commits. | ||||||||||||||||||
| # | ||||||||||||||||||
| # Composes with: | ||||||||||||||||||
| # - tools/hygiene/check-tick-history-order.sh (CI gate | ||||||||||||||||||
| # that catches violations from any append path, not | ||||||||||||||||||
| # just this one) | ||||||||||||||||||
|
|
||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||
|
|
||||||||||||||||||
| if [[ $# -ne 1 ]]; then | ||||||||||||||||||
| echo "usage: $0 \"<full row text including leading | and trailing |>\"" >&2 | ||||||||||||||||||
| exit 2 | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| ROW="$1" | ||||||||||||||||||
| REPO_ROOT="$(git rev-parse --show-toplevel)" | ||||||||||||||||||
| TICK_FILE="${REPO_ROOT}/docs/hygiene-history/loop-tick-history.md" | ||||||||||||||||||
|
|
||||||||||||||||||
| # Extract candidate timestamp from row | ||||||||||||||||||
| if [[ ! "$ROW" =~ ^\|\ ([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z) ]]; then | ||||||||||||||||||
| echo "ERROR: row must start with '| YYYY-MM-DDTHH:MM:SSZ '" >&2 | ||||||||||||||||||
| echo "got: ${ROW:0:80}..." >&2 | ||||||||||||||||||
| exit 1 | ||||||||||||||||||
| fi | ||||||||||||||||||
| NEW_TS="${BASH_REMATCH[1]}" | ||||||||||||||||||
|
|
||||||||||||||||||
| # Find latest existing timestamp | ||||||||||||||||||
| LATEST_TS=$( | ||||||||||||||||||
| grep -oE '^\| [0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z' "$TICK_FILE" \ | ||||||||||||||||||
| | sed 's/^| //' \ | ||||||||||||||||||
| | sort \ | ||||||||||||||||||
| | tail -1 | ||||||||||||||||||
|
Comment on lines
+57
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The helper exits before appending when Useful? React with 👍 / 👎. |
||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| if [[ -n "$LATEST_TS" && "$NEW_TS" < "$LATEST_TS" ]]; then | ||||||||||||||||||
| echo "ERROR: new row timestamp $NEW_TS is BEFORE latest existing $LATEST_TS" >&2 | ||||||||||||||||||
| echo "" >&2 | ||||||||||||||||||
| echo "Tick-history is append-only with non-decreasing timestamps." >&2 | ||||||||||||||||||
| echo "If your row is for a past tick, you have to either:" >&2 | ||||||||||||||||||
| echo " (a) update the timestamp to current UTC (preferred)," >&2 | ||||||||||||||||||
| echo " (b) file an ADR explaining the back-dated correction" >&2 | ||||||||||||||||||
| echo " and use a correction-row pattern per Otto-229." >&2 | ||||||||||||||||||
| exit 1 | ||||||||||||||||||
| fi | ||||||||||||||||||
|
|
||||||||||||||||||
| # Append using heredoc (the whole point of this script — bash | ||||||||||||||||||
| # heredoc is the canonical chronological-tail-append pattern) | ||||||||||||||||||
| cat >> "$TICK_FILE" << EOF | ||||||||||||||||||
| $ROW | ||||||||||||||||||
| EOF | ||||||||||||||||||
|
Comment on lines
+75
to
+79
|
||||||||||||||||||
| # Append using heredoc (the whole point of this script — bash | |
| # heredoc is the canonical chronological-tail-append pattern) | |
| cat >> "$TICK_FILE" << EOF | |
| $ROW | |
| EOF | |
| # Append the row literally so caller-provided content is treated | |
| # as data, not shell syntax. | |
| printf '%s\n' "$ROW" >> "$TICK_FILE" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,179 @@ | ||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # tools/hygiene/check-tick-history-order.sh — validates that | ||||||||||||||||||||||||||
| # docs/hygiene-history/loop-tick-history.md rows appear in | ||||||||||||||||||||||||||
| # non-decreasing chronological order (ISO-8601 UTC timestamps). | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Why this exists (Aaron 2026-04-26): | ||||||||||||||||||||||||||
| # The Edit tool's natural pattern (old_string=existing-line) | ||||||||||||||||||||||||||
| # tends to insert NEW content BEFORE the matched line, which | ||||||||||||||||||||||||||
| # produces reverse-chronological order when appending to the | ||||||||||||||||||||||||||
| # end of a tick-history table. I caught this bug at least three | ||||||||||||||||||||||||||
| # times across recent ticks and patched each occurrence by | ||||||||||||||||||||||||||
| # hand. Aaron asked: "anything we can do to prevent it in the | ||||||||||||||||||||||||||
| # first place?" The honest structural answer is a CI check that | ||||||||||||||||||||||||||
| # makes the bug fail fast at commit/push time instead of | ||||||||||||||||||||||||||
| # relying on each agent's vigilance. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # What this checks: | ||||||||||||||||||||||||||
| # - Every row matching the ISO-8601 timestamp prefix | ||||||||||||||||||||||||||
| # `| YYYY-MM-DDTHH:MM:SSZ (...)` is extracted in file order | ||||||||||||||||||||||||||
| # - Timestamps must be non-decreasing (allows duplicates from | ||||||||||||||||||||||||||
| # close ticks; forbids out-of-order) | ||||||||||||||||||||||||||
| # - Reports first violation with surrounding context and | ||||||||||||||||||||||||||
| # exits non-zero on failure | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
|
Comment on lines
+18
to
+25
|
||||||||||||||||||||||||||
| # What this does NOT do: | ||||||||||||||||||||||||||
| # - Does NOT re-order rows automatically. The fix is the | ||||||||||||||||||||||||||
| # committer's responsibility (revert + re-append correctly). | ||||||||||||||||||||||||||
| # Auto-reordering would silently rewrite history; this check | ||||||||||||||||||||||||||
| # is intentionally advisory-with-teeth. | ||||||||||||||||||||||||||
| # - Does NOT validate row content beyond the timestamp. | ||||||||||||||||||||||||||
| # Markdownlint and other lints handle table structure. | ||||||||||||||||||||||||||
| # - Does NOT enforce a strict-increasing rule. Two ticks at | ||||||||||||||||||||||||||
| # the same UTC second are rare but possible and not an error. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Composes with: | ||||||||||||||||||||||||||
| # - tools/hygiene/audit-tick-history-bounded-growth.sh | ||||||||||||||||||||||||||
| # (line-count threshold; this script is the order check | ||||||||||||||||||||||||||
| # that complements that one) | ||||||||||||||||||||||||||
| # - .github/workflows/gate.yml (wired as a lint job) | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Self-test: | ||||||||||||||||||||||||||
| # $ tools/hygiene/check-tick-history-order.sh | ||||||||||||||||||||||||||
| # → exit 0 if order is fine | ||||||||||||||||||||||||||
| # → exit 1 with diagnostic if any row is out of order | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # --strict: also report (advisory) historical strict-order violations | ||||||||||||||||||||||||||
| # anywhere in the file. Default is quiet because Otto-229 forbids | ||||||||||||||||||||||||||
| # editing prior rows so historical disorder cannot be repaired — | ||||||||||||||||||||||||||
| # reporting it on every CI run is noise. Aaron 2026-04-26: "we | ||||||||||||||||||||||||||
| # might should allow this one override if it exists a lot." | ||||||||||||||||||||||||||
|
Comment on lines
+49
to
+53
|
||||||||||||||||||||||||||
| # --strict: also report (advisory) historical strict-order violations | |
| # anywhere in the file. Default is quiet because Otto-229 forbids | |
| # editing prior rows so historical disorder cannot be repaired — | |
| # reporting it on every CI run is noise. Aaron 2026-04-26: "we | |
| # might should allow this one override if it exists a lot." | |
| # --strict: report (advisory) historical strict-order violations | |
| # anywhere in the file with full detail. Default mode does not | |
| # fail on that historical disorder, but it may still print a brief | |
| # advisory summary when such violations exist. Otto-229 forbids | |
| # editing prior rows, so repeated historical findings are mostly | |
| # noise unless explicitly requested. Aaron 2026-04-26: "we might | |
| # should allow this one override if it exists a lot." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1 (codebase convention): This workflow comment includes direct contributor name attribution ("Aaron …"). Repo operational rule is to avoid names on current-state surfaces like workflows/scripts and use role references (e.g., "human maintainer"), reserving names for the explicitly enumerated history surfaces (docs/AGENT-BEST-PRACTICES.md:284-347). Please rewrite these attributions here (and in the new hygiene scripts) to role-refs.