Skip to content

v1.3.0: when the headline is a NULL, assert extraction completeness - #28

Merged
wan-huiyan merged 2 commits into
mainfrom
learn/null-findings-extraction-completeness
Aug 7, 2026
Merged

v1.3.0: when the headline is a NULL, assert extraction completeness#28
wan-huiyan merged 2 commits into
mainfrom
learn/null-findings-extraction-completeness

Conversation

@wan-huiyan

Copy link
Copy Markdown
Owner

The nine-step protocol defends against a finding that is surprising but wrong. A null"X does not predict Y" — fails the other way, and steps 1–8 will not catch it.

The gap

Under-extraction manufactures nulls. Every exposed row your parser silently drops weakens the association you are testing, so a quiet bug reads as a clean negative — and a clean negative is precisely the answer that stops further work.

This is not step 6's coverage-limited join (that is about a subset being observable). It is your own extraction dropping rows it should have had.

The instance

A log parser lost 19 of 218 marker lines in silence: the source format omits a rotation clause at zero degrees, and over-budget records terminate on a line naming no coordinates — so whole blocks were skipped. The totals looked entirely plausible. Left unchecked it would have thinned the exposed group and made an already-null result look cleaner than it was.

The fix is an assertion, in code, not by eye:

assert n_parsed_exposures == raw.count("<the marker>")   # 50 == 50
assert n_parsed_outcomes  == raw.count("<the marker>")   # 168 == 168

And how to aim step 7 at a null

Build the second construction with the opposite bias — deliberately over-inclusive. If a construction that over-attributes exposure still lands on the same answer, the null is not an artifact of under-extraction in either.

In the case above: attribution by log structure gave 29 units, attribution by geometry gave 40, they agreed on only 19 — and both returned the same headline (17% and 18% against a 20% base rate).

Also

  • New red-flag table row for the clean-null case.
  • Cache and source were verified byte-identical before editing, per the rule session-handoff v1.19.0 now carries — the cache can be ahead of the source, and on another skill this week it was, by an entire step.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kj3iwhCeL8ugNZyUiNfCQf

wan-huiyan and others added 2 commits August 6, 2026 14:51
…lls alone (1.4.0)

A skill installed as a plugin lives at
~/.claude/plugins/cache/<marketplace>/<plugin>/<version>/, not at
~/.claude/skills/<name>/. Anything that reaches a skill through the
~/.claude/skills/ root alone misses on a plugin install.

The one with teeth — overnight-insight-discovery's Phase 0.Y toolchain
pre-flight decided whether the skill was installed with a single
`test -f ~/.claude/skills/overnight-insight-discovery/SKILL.md`. On a plugin
install that test fails, and the failure path is a tap-out with [ENV_BLOCKER]
reporting no skill tree: a failed lookup reported as an install-state finding.
It now probes $CLAUDE_PLUGIN_ROOT, then ~/.claude/skills/, then the plugin
cache, and on a miss prints "not found - tried <the three paths>".

Three details in the snippet each fix a real defect and should not be
simplified away: it ranks cache hits on the version path segment alone
(the marketplace segment sorts first, so a plain `sort -V` over whole paths
would let aaa-mkt/2.5.0 lose to zzz-mkt/1.0.0), it uses find rather than a
shell glob (zsh's nomatch fails a non-matching glob at expansion time, before
2>/dev/null can apply), and the miss message names the paths tried instead of
claiming "not installed".

Dead documentation links — three see-also entries in
schedule-poll-orchestrator-pattern pointed at ~/.claude/skills/<name>/SKILL.md
files a reader on a plugin install cannot open. No local path resolves under
every install method, so they are now plain skill names with a GitHub URL
where the source repo is known. Also removed one ~/.claude/skills/-rooted
self-reference from overnight-insight-discovery's v1.3.2 changelog entry.

Deliberately unchanged: every ~/.claude/skills/** mention in
§ "Autonomous-safe skill edits" and its Phase G summary (SKILL.md 570, 596,
606, 610, 810). Those are the path patterns that fire a sensitive-file
permission prompt in Claude Code - a fact about the prompt system, not about
where this skill is installed. Broadening them would break the contract.

CLAUDE_PLUGIN_ROOT alone is not the fix: it is frequently unset in the shell
a step actually runs in, and it points at the running plugin's own root, so it
can never reach a sibling plugin.

Versions: overnight-insight-discovery 1.1.1 -> 1.2.0 (manifests) and
1.8.0 -> 1.9.0 (SKILL frontmatter), schedule-poll-orchestrator-pattern
1.0.0 -> 1.0.1 (manifests + frontmatter), bundle VERSION 1.3.1 -> 1.4.0.
Minor because the pre-flight check's behaviour changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nine-step protocol defends against a finding that is surprising but wrong.
A null fails the other way and steps 1-8 do not catch it: under-extraction
manufactures nulls. Every exposed row a parser silently drops weakens the
association being tested, so a quiet bug reads as a clean negative — and a
clean negative is the answer that stops further work.

This is not step 6's coverage-limited join, which is about a subset being
observable. It is your own extraction dropping rows it should have had.

The instance behind it: a log parser lost 19 of 218 marker lines in silence,
because the source format omits a rotation clause at zero degrees and
over-budget records terminate on a line naming no coordinates, so whole blocks
were skipped. The totals looked plausible. Unchecked it would have thinned the
exposed group and made an already-null result look cleaner than it was. The fix
is an assertion, in code, that parsed counts equal the raw source's counts.

Also records that step 7 earns the most on a null, and how to aim it: build the
second construction with the OPPOSITE bias — deliberately over-inclusive. In
the case above, attribution by log structure gave 29 units and attribution by
geometry gave 40, agreeing on only 19, and both returned the same headline
(17% and 18% against a 20% base rate). A construction that over-attributes
exposure landing on the same answer is what rules out under-extraction.

Cache and source were byte-identical before this edit (checked, per the rule
session-handoff v1.19.0 now carries).
@wan-huiyan
wan-huiyan merged commit ff5e573 into main Aug 7, 2026
1 check passed
wan-huiyan added a commit that referenced this pull request Aug 7, 2026
…ence your work survived it (1.4.0) (#29)

Adds "Pre-flight: stale-base audit — what your OWN branch deletes", placed
immediately after the parallel-branch file-collision audit as its inward mirror.
That audit looks outward at branches that might conflict with you; this one looks
at the branch you are about to merge.

THE EVIDENCE IS ONE INCIDENT, 2026-08-07. A pull request merged from a branch
created before several other pull requests landed and never rebased; its conflict
resolution took its own side across the whole tree. 59 files, 1,891 insertions,
5,081 deletions. It reverted 11 files and 15 tracker entries belonging to three
other sessions -- source modules, their tests, analysis pages, handoff prompts,
tracker rows -- plus a function two surviving files still imported. Nothing
failed: no conflict, green PR, schema validator passed, site still rendered. All
three sessions had finished a wrap-up that morning and their work WAS on main,
for between 6 and 90 minutes. The merged content was legitimate and had to stand,
so git revert was the wrong tool.

TWO DOTS, NOT THREE -- a correction the drafting turned up. The brief for this
change proposed `git diff origin/main...HEAD --diff-filter=D`. Three dots diffs
from the MERGE BASE, so on a branch that never took main's newer commits a file
added to main after the branch point is absent from both sides and reports as no
change at all. Verified on a two-commit synthetic repo: three-dot prints nothing,
two-dot prints the file. The section ships the two-dot form, rebase-first
ordering, and the note that a plain merge of a stale branch is harmless -- the
damage needs the branch's tree to win wholesale.

THE EXISTENCE CHECK IS THE WEAK ONE. `git cat-file -e` asks only whether a path
exists; a file can be present with its contents rolled back and no existence
check, id check or validator will report it. Audit instead for a marker your
change ADDED. Worked example, measured rather than argued: a page of seven
interactive widgets whose option lists are single-quoted HTML attributes, three
holding an apostrophe behind a one-character `&#39;`. Roll that escape back on
the fourth widget and the file exists, the HTML is valid, all seven widgets are
in the markup, every committed check passes -- and four of the seven are silently
dead, because the truncated attribute throws inside the one forEach that builds
them all. Loading the real page with each escape rolled back in turn gives three
working, or none if the first widget is the one broken.

Recovery is a splice-forward (`git show <sha>:<path>`), never a revert.

Version: SKILL frontmatter, plugin.json and marketplace.json all 1.3.1 -> 1.4.0.
Bundle VERSION already reads 1.4.0 from the 2026-08-06 release and is unchanged:
it is the bundle's own counter, not a mirror of this plugin's version, and the
release one day earlier (#28) shipped a plugin bump without touching it.

Gates: check_skill_descriptions --triggers exit 0 (description byte-identical,
1,463 chars, 8 triggers unchanged per --compare); leak_scan clean;
validate_plugins OK, 0 warnings.


Claude-Session: https://claude.ai/code/session_01UCqwdBqETB9SeEVR2fSTfK

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant