Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
c635aa8
feat(pr-workflow): add pr-validate and falsifying-test skills
MajorLift Jul 30, 2026
26a7daf
Restore the MetaMask-planning link in the step-waiver item
MajorLift Jul 30, 2026
a66e72a
Move `falsifying-test` to the `testing` domain
MajorLift Jul 30, 2026
78a93b0
Add `D6` substitution A/B lane and sync `pr-validate` references
MajorLift Jul 30, 2026
36ea9c8
Move the AEP run procedure behind a reference
MajorLift Jul 30, 2026
2af1db1
Restore the full `pr-validate` description
MajorLift Jul 31, 2026
0b4bb26
Actually restore the `pr-validate` description
MajorLift Jul 31, 2026
704128a
Rename `pr-validate` to `evidence`
MajorLift Jul 31, 2026
e7e9248
Repair unresolvable references in `evidence` and `falsifying-test`
MajorLift Jul 31, 2026
1e927d5
Follow the engine renames in the catalog and `falsifying-test`
MajorLift Jul 31, 2026
14e670a
Add a lane index to the evidence catalog, and fix lane placement
MajorLift Jul 31, 2026
eceaf37
Add build-duration lanes `D7` and `G6`
MajorLift Jul 31, 2026
90d7082
Inline the publishing non-negotiables, which a real run ignored entirely
MajorLift Aug 1, 2026
d412caf
Add `falsify-probe.sh` — the runner that makes a lane reproducible
MajorLift Aug 1, 2026
559b4c0
Add `capture.sh` so the C9 and D3 analyses stop needing an operator t…
MajorLift Aug 1, 2026
85a575c
Add `selector-recompute.sh` — lane C4 gets a runner
MajorLift Aug 1, 2026
94a7640
Add `tsc-substitution.sh` — a runner for the tsc-blindspots lane
MajorLift Aug 1, 2026
6e38d6d
Diff the error sets rather than requiring a clean baseline
MajorLift Aug 1, 2026
3f861c7
Add `attest-gate.sh` — eight mechanical checks before anything is pub…
MajorLift Aug 1, 2026
2c0f286
Standardise the provenance marker across every runner
MajorLift Aug 1, 2026
51d6f94
Add `render-count.sh` — the component half of lane C4
MajorLift Aug 1, 2026
e5b6ce1
Guard the runners against the failures that masquerade as findings
MajorLift Aug 1, 2026
d84f304
Give the orchestrator a runner registry, with limits and synthesis rules
MajorLift Aug 1, 2026
0d8935f
State the bar: float concerns, do not close them
MajorLift Aug 1, 2026
6bdffd1
Report what each measurement runner did not cover
MajorLift Aug 1, 2026
30a03b6
Require a validation run to float something for review
MajorLift Aug 1, 2026
d5c6a61
Stop the runners from cutting the part a reader needed
MajorLift Aug 1, 2026
a3f1590
Reference artifacts by name in the publishable block
MajorLift Aug 1, 2026
d55b324
Stop the gate failing runs whose environment is not the repo's
MajorLift Aug 1, 2026
4ae5373
Let the caller say where its tool puts the finding
MajorLift Aug 1, 2026
d32bdf7
Write for the reviewer, not for a user of this skill
MajorLift Aug 1, 2026
7dee1f9
Move the output template into the skill, where corrections can land
MajorLift Aug 1, 2026
2c72486
Require a finding, not a printout
MajorLift Aug 1, 2026
4132951
Check where the disclaimer is, not just that it is there
MajorLift Aug 1, 2026
e58d1c0
Stop a script's summary of itself passing as a capture
MajorLift Aug 1, 2026
3c86466
Ask for a medium, not for better text
MajorLift Aug 1, 2026
dbc2754
Move the measurement to CI, where the run URL is the capture
MajorLift Aug 2, 2026
bdf5f39
Pin the runner source, and say so when it is missing
MajorLift Aug 2, 2026
5c41bfb
Require the target repo, make install opt-out, drop the consumer-inst…
MajorLift Aug 2, 2026
8d1ec29
Make every runner say whether a reader can verify it
MajorLift Aug 2, 2026
56578ce
Cite what exists; capture what you ran
MajorLift Aug 2, 2026
f6d76e8
Fold the runner-workflow fixes back from where they were found
MajorLift Aug 2, 2026
0b874cf
Name the commit in the command line, so an A/B pair is not one line t…
MajorLift Aug 2, 2026
a64ddb8
Stop a broken substitution reading as a divergence
MajorLift Aug 2, 2026
bfdf6b4
Require a run to measure the PR's range, and to say where its reach ends
MajorLift Aug 2, 2026
faff610
Require an instrument to publish the effect it had, not the instructi…
MajorLift Aug 3, 2026
46372af
Point `falsifying-test` at the harness, and separate it from its sibling
MajorLift Aug 3, 2026
1e7ef54
Rename `falsifying-test` to `red-on-base`
MajorLift Aug 3, 2026
50b52dc
Move `red-on-base` out; this PR is the instrument
MajorLift Aug 3, 2026
594a1a7
Check where the run is going, not only what it says
MajorLift Aug 3, 2026
0dee42c
Bring the runner fixes back from the branch CI was actually running
MajorLift Aug 3, 2026
cae0c08
Check that a figure in the prose traces to an exhibit
MajorLift Aug 4, 2026
a5bfb98
Stop check 12 passing when it could not run
MajorLift Aug 4, 2026
7da6ee1
Point references at the renamed `lavamoat-policy` skill
MajorLift Aug 4, 2026
38316ee
Give the diligence format a gate with `attest-gate.sh --diligence`
MajorLift Aug 4, 2026
76ecd0a
Remove private-repo and personal references from a public skill
MajorLift Aug 4, 2026
fbf8909
Take the artifact bucket and test fork out of the published text
MajorLift Aug 4, 2026
6634203
Restore what the privacy scrub broke: a working jq filter and bucket …
MajorLift Aug 4, 2026
d5fa3a5
Enforce the evidence rules where the model cannot route around them
MajorLift Aug 4, 2026
27be7ae
Add `attest` — the publish gate, ported from a personal command
MajorLift Aug 4, 2026
e069061
Add `gate-controls.sh`, and scope the gate to evidence artifacts
MajorLift Aug 4, 2026
2bb5a29
Check that the gate is wired, not only that it works
MajorLift Aug 4, 2026
d3d153d
Fail closed when the body cannot be read
MajorLift Aug 5, 2026
d5e504a
Add two PR-audit skills that read the description in opposite orders
MajorLift Aug 3, 2026
543df53
Add `silent-failure`, which tests detectability rather than correctness
MajorLift Aug 3, 2026
a1f859d
Add five reasoning audits and two diagnostic skills
MajorLift Aug 3, 2026
70ed100
Take `red-on-base` from #84, where it was the odd one out
MajorLift Aug 3, 2026
5899fd5
Name the installed command in `red-on-base`'s description
MajorLift Aug 3, 2026
1575e3b
Add `reasoning-audit`, a router for the reasoning skills
MajorLift Aug 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions domains/coding/skills/distinguishing-observation/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
name: distinguishing-observation
description: Enumerate every mechanism that could produce a symptom, then design the observation that separates them — instead of instrumenting the one mechanism you already suspect. An observation your favourite hypothesis predicts, and the alternatives predict too, costs a debugging cycle and buys nothing. Use when a bug has more than one plausible cause, when you are about to add a log line to confirm a suspicion, when a fix landed and the symptom did not move, when every result so far "is consistent with" the theory you started with, or when the symptom looks impossible given your model of the system. Ranks observations by how much they split the candidate set rather than by how easy they are to collect, requires a per-candidate prediction written before looking, and records survivors as not-yet-distinguished rather than ruled out.
---

# /distinguishing-observation

Given a symptom, the instinct is to instrument the mechanism you already suspect. That produces
evidence consistent with your hypothesis — and equally consistent with three others you never
wrote down.

This is the diagnostic mirror of hypothesis-first validation. There, you fix the hypothesis
before seeing what it will be compared against. Here, you fix the *candidate set* before
choosing what to measure, because the value of an observation is a property of the whole set and
cannot be judged against one member of it.

## The information is in the split

A confirming observation feels like progress and usually is not. If four mechanisms could produce
this symptom and your log line fires under all four, you have learned that the code ran. You
already knew that; the symptom told you.

The observation worth making is the one whose outcome you cannot predict, because the candidates
disagree about it. That is the only kind that costs a cycle and returns a cycle's worth of
information. Debugging that never converges is almost always a sequence of observations each of
which was compatible with everything.

## The discipline

1. **List the candidates before instrumenting.** Three to six mechanisms that could produce this
symptom. A list of one is not a list, it is a conclusion — and you will spend the next hour
collecting support for it.

2. **For each pair, write what differs.** Not what you believe about each; what the world would
look like differently. If two candidates predict identical observations *everywhere*, they are
not distinguishable by observation at all, and you need either a different pair or a different
axis — often a level lower, where the two mechanisms stop coinciding.

3. **Rank observations by how much they split the field**, not by how easy they are to collect.
The best observation halves the candidate set. The worst confirms the favourite. Cheapness is
worth something, but a cheap observation with no discriminating power is not cheap, it is free
and worthless.

4. **Predict before you look.** Write down what each candidate predicts for the observation you
are about to make, then make it. Doing this after the fact is how every result becomes
consistent with the hypothesis you started with — the prediction is elastic until it is
written down, and reading the output first sets it.

5. **A candidate that survives is not eliminated.** Say "not distinguished by this observation",
never "ruled out". The observation constrained what it constrained. This wording is not
pedantry: when the bug comes back in three weeks, a list of things "ruled out" is a list you
will not revisit, and the real mechanism is usually on it.

## Pairs that look identical from outside

These shapes recur, and knowing them saves the cycle you would spend rediscovering that your
evidence does not separate them. In each case the fix is to add the separating signal *before*
continuing — which is routinely faster than more reading.

| indistinguishable pair | why the evidence coincides | what separates them |
|---|---|---|
| an error swallowed by a `catch` vs. a code path never reached | both produce no output, no error, and no trace | count entries to the `try`, not exits from the `catch`: entered-and-never-completed is the first, never-entered is the second |
| a cache hit vs. a correct recomputation | the returned value is the same value | poison the entry with a marker only a hit could return, or count invocations of the compute function |
| a retry that succeeded vs. a call that never failed | both end in one success log | log the attempt number, not the outcome — success on attempt 1 and success on attempt 3 are different worlds |
| a timing-dependent bug vs. a state-dependent bug | both reproduce "sometimes" | hold one axis fixed: a fresh process per run under varying load isolates timing; repeated runs in one process isolate accumulated state |
| the wrong value vs. the right value from the wrong source | the assertion fails the same way | print provenance alongside the value — which module, which config, which build |
| a change that had no effect vs. a change that never shipped | the symptom is unmoved either way | verify delivery first (hash, timestamp, a deliberate marker in the artifact); an undelivered treatment reads exactly like a null result |

The last row generalises: **before concluding that a mechanism does not matter, prove the
mechanism was present.** Otherwise "no effect" and "not applied" are the same measurement.

## The anti-pattern: the observation that always fires

The tell is a log line you added, that printed, and that made you feel confirmed. Ask what would
have had to appear instead for you to abandon the hypothesis. If the answer is "nothing" — if
every candidate on your list predicts this exact output — the observation had no capacity to
discriminate and the confidence it produced is manufactured.

This is why step 4 is ordered where it is. A prediction table written first makes an
always-fires observation obvious before you spend the cycle: the column is identical all the way
down, and you go find a different one.

## When the candidate set is empty

Sometimes you enumerate and get nothing: the symptom is impossible given your model of the
system. That is not a dead end, it is the most informative result available, because it means the
model is wrong and you now know it.

Switch the question from "which mechanism did this" to **"what would have to be true for this to
happen at all"**, and enumerate *those*. The answers are usually assumptions you did not know you
were making — the built artifact is not the source you are editing, two copies of the module are
loaded, the process you are reading logs from is not the process serving the request, the
environment differs from the one you configured. Each is checkable, and one of them is the bug.

## Related

- [`flaky-test-detection`](../flaky-test-detection/skill.md) — the timing-vs-state pair applied
to one domain, where "reproduces sometimes" is the starting symptom rather than a row in a table
- [`falsifiers-first`](../../../pr-workflow/skills/falsifiers-first/skill.md) — the same sealing
discipline pointed at a change instead of a symptom: fix the hypotheses before seeing what they
will be compared against
- [`silent-failure`](../../../pr-workflow/skills/silent-failure/skill.md) — supplies the first
row of the table as a subject in its own right, and asks whether a mechanism announces its own
failure at all
- [`evidence`](../../../pr-workflow/skills/evidence/skill.md) — the runners that collect the
chosen observation and attach the prediction made before it
114 changes: 114 additions & 0 deletions domains/coding/skills/observability-gap/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
name: observability-gap
description: Before debugging a path, establish what signal already exists on it — logs, metrics, error reporting, test coverage, user-visible state — and treat the blanks in that inventory as the first finding. A bug you cannot see is a bug you will fix by guessing, so once reading has stopped narrowing the search, the productive move is to install signal rather than read further. Separates absent signal from suppressed signal — filtered by level, sample rate, a feature flag, or an error-swallowing wrapper — because they are different problems with different fixes, and the suppressed one is both more common and more expensive. Use when a bug reproduces but its cause is invisible, when a report arrives with no trace attached, when reading code has stopped eliminating candidates, or when instrumentation appears to exist and the environment where the bug happens is emitting none of it.
---

# /observability-gap

The first question about a bug is not "where is it". It is **what would have told me**.

A path you cannot see is a path you will fix by guessing, and a guess that happens to make the
symptom go away is indistinguishable from a fix until it comes back. The opening move on an
unobservable path is usually to make it observable — not because instrumentation is virtuous,
but because every subsequent step is cheaper once the path reports on itself.

## Inventory the signal before the code

For the path under investigation, write the list before reading further:

| signal | what to check | a blank here means |
|---|---|---|
| logs | is anything written on this path, at what level | the path runs and leaves no trace |
| metrics / traces | is the operation counted, timed, spanned | you cannot tell how often, or whether it is getting worse |
| error reporting | does a failure here reach Sentry or equivalent | failures are counted by users, not by you |
| tests | does anything execute this path at all | you cannot reproduce without the full system |
| user-visible state | does the UI or the API response differ when this goes wrong | the only detector is a human noticing |

The list is not the deliverable. **The blanks are the finding**, and a path with five blanks is
not a hard bug, it is an unobservable one — a different problem with a different first move.

## Absent is not suppressed

No log line, and a log line nobody sees, look identical from where you are sitting. They are
not the same problem:

- **Absent** — the code never emits. The fix is to write the emission, and it lands in the diff.
- **Suppressed** — the code emits and something eats it: a level filter, a sample rate, a
feature flag or env gate, a transport pointed at a sink nobody reads, or a `catch` that
consumes the error before anything can report it. The fix is usually a config change, often
one line, sometimes in a repo you do not own.

Suppressed is the more common case and by far the more frustrating, because the codebase reads
as instrumented. Grep found the log line. The line is there. It is just not reaching you, and
every minute spent explaining why the code "should" be logging is spent on the wrong question.
Establish which of the two you have before proposing anything.

## Read until it stops narrowing, then install

Reading has a point of diminishing returns and it is easy to blow past, because reading feels
like progress in a way that writing a log line does not.

The tell is mechanical: **two consecutive passes over the same files that eliminate no
candidate**. At that point more reading is not going to produce the answer, and the cheapest
remaining move is to add signal and run it again. One log line at the right boundary routinely
settles a question that an hour of reading left open, because it reports what actually
happened rather than what the code permits to happen.

## Instrument the boundary, not the suspect

Put signal at the **edges of the subsystem**, not on the line you suspect.

A boundary tells you whether the problem is inside or outside, which halves the search
regardless of whether your hypothesis was right. Signal on your favourite line tells you about
that line only, and only in the case where you had already guessed correctly — which is the
case where you needed the least help. Instrument in and out first; narrow after the halving.

## The gaps worth naming

| class | why it costs you |
|---|---|
| a failure path with no error reporting | the failure is real and the count is zero |
| an async boundary that loses context | the error surfaces detached from its cause, pointing at the awaiting frame instead of the failing one |
| a conditional whose branch is not recorded | you cannot tell which way it went, so both explanations survive |
| state mutated with no trace of the mutator | you can see the wrong value and not who wrote it |
| a third-party call whose failure mode is a default return | a degraded dependency is indistinguishable from an empty result |
| instrumentation that is off in the environment with the bug | the signal appears to exist |

The last one is the most expensive in this table, and the reason is in the phrasing: the others
announce themselves as gaps once you look, and this one does not. You find the log line, you
assume the path is covered, and you spend the afternoon reasoning about why the covered path
produced no output.

### The environment check

Confirm the signal is on **in the environment where the bug happens**, not in the one where you
are reading the code. A metric emitted only in production and a log emitted only in development
are both silence exactly where you need them — and each looks like working instrumentation from
the other side.

Concretely, for each signal you are counting on: which env vars, flags, log levels, sample
rates and build modes gate it, and what are their values *on the machine that reproduced the
bug*. If you cannot answer that, you do not know that the signal exists there; you know it
exists in the source.

## Keeping what you added

A signal you add to find a bug is a signal the next person needs. Decide deliberately before
removing it, and default to keeping it: the path was hard to debug **because** it was
unobservable, and reverting the instrumentation restores precisely that condition for whoever
arrives next.

Reasons to remove are real but specific — a per-iteration log in a hot loop, output containing
user data, a metric whose cardinality is unbounded. "It was only for debugging" is not one of
them. If the volume is the problem, lower the level or gate it behind a sample rate rather than
deleting it, so the next person can turn it back on instead of rediscovering the gap.

## Related

- [`silent-failure`](../../../pr-workflow/skills/silent-failure/skill.md) — the review-facing
sibling. Same property, opposite end: it asks whether a mechanism would announce its own
failure, this one starts from a failure that already happened and nobody saw
- [`falsifiers-first`](../../../pr-workflow/skills/falsifiers-first/skill.md) — once the path
reports on itself, hypotheses about it become testable rather than arguable
- [`flaky-test-detection`](../flaky-test-detection/skill.md) — the same gap inside a suite,
where the missing signal is what the test observed on the run that failed
41 changes: 41 additions & 0 deletions domains/pr-workflow/skills/attest/references/dispatched-passes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Phase 1 — the three dispatched briefs

Send each to a **fresh instance** with the artifact and nothing else: not the transcript, not
your reasoning, not what you expect it to find. Context is what you are testing for. An instance
that knows what you meant will read what you meant.

Run them concurrently — they are independent, and sequencing lets the first one's findings frame
the others.

## outframe — contest the frame

> You are reading a finished set of findings you did not produce. Do not check whether the
> findings are correct. Ask what claim was chosen and what a different framing makes visible:
> what question would a reader with different priorities have asked of the same material, what
> does the chosen frame make it impossible to notice, and which of the findings only look
> significant because of how the problem was cut. Return findings the framing hid, not a
> critique of the writing.

## missing — contest the coverage

> You are auditing a completed run for what it did not do. Enumerate: a modality that was not
> run, a claim asserted but not verified, a source cited but not read, a case the method
> structurally cannot reach. For each, say what running it would cost and what it could change.
> Do not restate what the run found. Absence is the deliverable.

## press — read it as the stranger

> You are the reviewer this lands in front of, with no context and a decision to make. Read only
> the artifact. Say what you would have to take on trust, which number you could not check if you
> wanted to, what reads as a measurement but is a sentence, and anything that assumes you were
> present for work you were not. Flag register slips: hedging that reads as concealment,
> confidence that outruns the evidence, and any place the author's process shows through.

## Reading the returns

A finding from any pass that invalidates the claim is `BLOCKED`. A finding that qualifies it is
`ATTESTED WITH` — and the caveat goes **into the published artifact**, not just into the verdict,
or the reader never sees it.

Disagreement between passes is signal, not noise: `press` clearing something `outframe` flagged
usually means the artifact reads well and is framed wrong, which is the more dangerous state.
35 changes: 35 additions & 0 deletions domains/pr-workflow/skills/attest/references/phase-0-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Phase 0 — what each check catches

Generated from the checks in `mms-evidence/scripts/attest-gate.sh`; that script is the
authority. Each entry exists because a run shipped without it.

| # | check | run mode | diligence mode |
|---|---|---|---|
| 1 | marker pair | ✓ | ✓ |
| 2 | canonical header | ✓ | ✓ |
| 3 | verdict line | ✓ | ✓ |
| 4 | citations pinned | ✓ | ✓ |
| 5 | captured artifact | ✓ | ✓ |
| 6 | no prescriptions | ✓ | ✓ |
| 7 | no process narration | ✓ | ✓ |
| 8 | verdict is earned | ✓ | ✓ |
| 9 | verdict matches artifact | ✓ | ✓ |
| 10 | floats something for review | ✓ | ✓ |
| 11 | disclaimer present and early | ✓ | ✓ |
| 12 | destination is open | ✓ | ✓ |
| 13 | figures trace to an exhibit | ✓ | ✓ |

Checks 1–4 differ by mode: in `--diligence` they test that contract's own marker pair, its
header, and that citations are pinned to a tag or SHA rather than a branch head, and the
verdict-line check reports SKIP because a diligence artifact renders none. Checks 8 and 9 SKIP
for the same reason. Everything from 5 down is shared, because those defects are shared.

**Check 5 is the one that matters, and it asks for a medium.** Every earlier version tested a
property of the plaintext — does it carry a marker, does the command contain a placeholder — and
each caught one defect and missed the next, because every property of plaintext is forgeable by
whatever emits the plaintext. Four runs shipped that way. A `/blob/` permalink is a citation and
does not satisfy it: it witnesses a line in a file, never a run.

**Check 12 tests the destination**, which no property of the text reveals. Across one register of
published runs, 22 of 27 comments went to pull requests that had already merged — median 22 days
after the merge, gate-clean every time.
Loading