Skip to content

task: the tier floor announces WHY it fired, and a design decision can appeal it on the record (DIVE-2089) - #252

Merged
lodar merged 2 commits into
mainfrom
dive-2089-floor-declared-discussion
Jul 27, 2026
Merged

task: the tier floor announces WHY it fired, and a design decision can appeal it on the record (DIVE-2089)#252
lodar merged 2 commits into
mainfrom
dive-2089-floor-declared-discussion

Conversation

@lodar

@lodar lodar commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Closes DIVE-2089. Merges BEFORE DIVE-2099 (PR #225) — 2099 ships inert if it lands first, because engineering gates keep arriving pinned at tier-2 where its new authority cannot reach them.

The defect

The T2 keyword floor reads SUBJECT MATTER as RISK and picks the audience from it. A gate asking "how should we model capability vs clearance" was routed as though it HANDLED a secret. Talking about credentials is not handling credentials. It fired silently, and the discovered workaround — reword until it routes correctly — teaches the fleet to launder vocabulary, which erodes the exact signal the floor exists to protect.

What this ships

  1. --discusses="<why>" on task need — a DECLARED appeal of a floor that fired on subject matter. Deliberately not a sixth keyword class. Four guards: --type=decision only (approval/manual/secret/access declare an ACTION, so the flag is REFUSED, not ignored); only on an actual over-fire; a non-appealable core (money / outbound comms / irreversible infra) re-tested on the residual; downgrade only to a LEAD-ROUTED tier 1. --tier=2 vetoes it. Every refusal is loud, the declaration is written into the ask the reviewer grades, and the outcome is audited applied OR refused.
  2. The floor now names the matched term on the result line and warns at file time, and tells a decision filer to appeal rather than reword.

How it was checked

tests/gate_floor_declared_discussion_unit.sh, 45 assertions. Every arm exercised on the ask axis AND the TITLE axis — a suite varying only the ask passes vacuously (DIVE-1957), and the title is the axis a filer cannot reword.

Graded by mutation, not by greenness. The verifier re-derived four load-bearing mutants independently: neutralising the downgrade → 8 red; neutralising the non-appealable core → 4 red; neutralising the decision-only guard → 5 red; blanking ok()'s prose branch → 5 red including liveness. The suite cannot pass by having disabled the floor.

Arm 7 keeps a refutation LIVE rather than in prose: if the floor is ever widened to match restart/session, it goes red.

Regression: 25/26 gate_*_unit.sh harnesses green, 392 assertions. gate_verifier_route_unit.sh exits rc=5 identically on base dfd3d30 and on this head — pre-existing, measured on both sides rather than taken on report.

Rebase note

Rebased onto dfd3d30. The generated bundle was REBUILT at each step, never hand-resolved — build.sh itself changed on main (DIVE-2102 registers src/lib/capability.sh), so a rebuild with the stale script emits 47,779 lines instead of 48,024 with capability_declare() absent, and still passes the sha check. Verified by arithmetic: bundle delta vs base == src delta vs base == 218.

The recursion, left standing on purpose

Pushing this branch required a human-only tier-2 approval because this ticket's own TITLE contains "credentials" and the floor matches over ask + title. Unclearable by the org lead, unfixable by rewording. Left as-is rather than reworded: laundering it is the behaviour this PR exists to stop.

🤖 Generated with Claude Code

lodar and others added 2 commits July 27, 2026 14:21
…l it on the record (DIVE-2089)

The T2 category floor reads SUBJECT MATTER as risk and picks the gate's
audience from it. dev3's tier-1 sizing gate — "should an agent's right to
act derive from the credentials it holds, or from a declared clearance
level?" — was forced hard-human because the ask contains "credentials"
and "privileged". It discusses credential handling as a design question
and performs no credential operation.

Two things made that worse than a mis-tier. It was SILENT: dev3 found out
by re-reading their own filed gate. And the workaround was to re-file with
neutral wording, which works, teaches the fleet to launder vocabulary to
reach the right audience, and leaves no trace of having been done.

- The floor now names the matched term, on the result and on stderr at
  file time. For a decision gate it states the sanctioned appeal and says
  not to reword the ask.
- --discusses="<why>" appeals a floor that fired on subject matter. A
  DECLARATION, not a sixth guesser: no phrasing reliably separates
  "discussing X" from "doing X". Unlike a reworded ask it is
  attributable, shown to the reviewer, and audited applied OR refused.
- Four guards: --type=decision only; only on an actual over-fire; never
  for money / outbound comms / irreversible infra; downgrades only to a
  LEAD-routed tier 1. An explicit --tier=2 vetoes it; refusals are loud.

Not a keyword class, deliberately — DIVE-2099 note 1: inferring this from
more vocabulary reproduces the bug with the polarity reversed, where a
false negative routes a real secret gate away from the human.

No existing gate changes tier: nothing moves without the new flag, which
discharges the DIVE-2146 precondition by construction. Measured while
checking it — the DIVE-2146 self-restart gate never tripped the floor;
it reached the human via an explicit --tier=2 re-file. Kept as a live
assertion so a future widening of the floor goes red there first.

tests/gate_floor_declared_discussion_unit.sh — 36 assertions, every arm on
the ask axis AND the TITLE axis (DIVE-1957).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…reads (DIVE-2089)

olivia's iteration-1 reject, and it was right. The floor announces on two
surfaces — the stderr warn at file time, and the `ok ... ${floor_note}`
result line — and all 36 assertions called cmd_task_need with stdout
suppressed, grepping only stderr. The result line had ZERO assertions.
Proved by mutation: stripping ${floor_term:+: matched '$floor_term'} out
of floor_note left 36 passed, 0 failed.

That is the wrong surface to leave unguarded. It is the one defect 2's own
discovery story runs through — dev3 found the escalation by RE-READING
their filed gate, not by catching a warn that had already scrolled past —
and the iteration-1 build note quoted this very line as its before/after
evidence.

There were TWO reasons it was unasserted, and the second is the one that
bites. The suite sets JSON_MODE=1 globally, under which ok() emits the jq
payload and never renders the prose at all. So the obvious repair — stop
discarding stdout — would ALSO have stayed green under olivia's mutation:
the surface is not merely uncaptured, it is unreachable. The arm has to
flip the mode, and the negative control needs a liveness assertion or it
passes vacuously on an empty string.

- 9 new assertions (36 -> 45): the result line states the floor fired and
  names the matched term, on the ask axis AND the TITLE axis (DIVE-1957);
  a negative control that an unfloored gate claims no floor; a liveness
  arm that the prose rendered at all.
- floor_term now rides the --json payload (null when nothing floored). An
  agent filing with --json got tier_floored:true and no way to learn which
  word did it, which leaves the machine reader in exactly the state this
  ticket exists to fix.

Mutation-graded, each red predicted before running: olivia's exact mutation
-> 2 red; ${floor_note} dropped from the result line -> 4 red; floor_term
dropped from the JSON payload -> 1 red; ok()'s prose branch blanked -> 5
red, including the liveness arm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lodar
lodar merged commit 7d96931 into main Jul 27, 2026
14 checks passed
@lodar
lodar deleted the dive-2089-floor-declared-discussion branch July 27, 2026 14:46
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