Skip to content

v0.5.1 — Layer R on plan-v1 (F50) + F49 ReDoS hardening#13

Merged
hamr0 merged 7 commits into
mainfrom
f49-f50-layer-r-planflow
Jul 23, 2026
Merged

v0.5.1 — Layer R on plan-v1 (F50) + F49 ReDoS hardening#13
hamr0 merged 7 commits into
mainfrom
f49-f50-layer-r-planflow

Conversation

@hamr0

@hamr0 hamr0 commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

Patch release closing two named items before Layer 3, plus a review-caught fix.

  • F50 — Layer R (layerRoot) wired into the accepted plan-v1 flow (runPlan): one root per EXECUTE step (red-set = the exit evaluator's own gap) and one in the outer close-fix loop (red-set = the close's gapKeep). Was silently ignored on the shipped path (wired only into legacy steps[]). Still OFF by default (F41); native/clipipe excluded (no onToolResult seam).
  • F49 — nested-quantifier ReDoS reject at the plan validation gate for the agent-authored artifact-written pattern. Input-bounding refuted as theater (33-char body hangs); reject is the doctrine-clean fix. Extended this branch after /code-review medium caught a false negative: redundant-wrapper forms (((a+))+, (?:(a+))*, (((a+)))+) slipped the flat-only scan and still hang >8s — closed monotonically (propagate the inner repeat up through the wrapper; only ever adds rejections, the fail-safe direction, cannot introduce a new false negative).
  • PRD v1.23 — the two workflow shapes; plan-v1 is the target for all verdicts, legacy steps[] sunsets at the verdict-classes rung (post-Layer-3).

Parked (documented, not fixed)

Gates (fresh full, no diff-only, no fit-to-pass)

🤖 Generated with Claude Code

hamr0 and others added 7 commits July 23, 2026 18:50
The agent-authored artifact-written `pattern` was compiled but not screened
for catastrophic backtracking; evalExits runs it with no wall-clock bound, so
a nested unbounded quantifier ((a+)+, (\d*)*, (x+){1,}) could hang the exit
evaluator. LOW self-DoS (agent authors both pattern and body; no arbiter
compromise), but a real hang: measured (a+)+$ NOT finishing on a 33-char body
in 120s.

Fix at the validation gate (before any tokens): plan.js runs a vanilla
hasNestedQuantifier() state-machine scan after the compile check and reds
invalid-value on the exponential class. Skips escaped atoms and character
classes; bounded outer repeats stay legal. Input-bounding rejected as theater
(the blowup needs only tens of chars); a JS regex timeout as disproportionate
(worker_threads/RE2 dep) for a LOW issue.

TDD: 12-bad/17-good detector battery + validator red-case + detail-names-the-
footgun test. 555/555, typecheck + build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
layerRoot was wired only into the legacy steps[] path (interpret.js); the
accepted plan flow (runPlan) silently ignored it, so a plan-shape job could
never emit root-injected and the pre-registered ON-vs-OFF default-flip read
was impossible on the go-forward surface — the blind-instrument class as a
silently-ignored optional param.

Wire it per step (each micro-wheel is the Layer-1 atom): the tee (stage/settle/
discard + onToolResult outcome probe) is mirrored from interpret.js into
mkWorker; executeStep creates one root per step and calls observe + injects the
note in the middle. Two plan-flow-specific calls, both traced not assumed:
  - red-set = the exit evaluator's OWN gap (gapKeep '\S', the whole normalized
    complaint) — a check's ^-anchored gapKeep does not survive the exit wrapper
    (check "x" red: FAILED …) and would degrade to the dangerous writes-only mode.
  - the tee is REQUIRED, not just for verbatim: a step rewrites its one target
    every attempt, and the cumulative audit dedups by path, so without the tee
    the summary stage never fires either.
Native/clipipe excluded (no onToolResult seam; F48 fallback surface).

Still OFF by default (F41). TDD: 5 tests through the real plan flow — summary
fires + reaches the next prompt, a third fixation escalates to verbatim
surfacing the worker's own teed bytes, OFF emits nothing, native inert.
Docs: CHANGELOG, LAYERS ⚠ note, FINDINGS F49→resolved + F50, context.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…over-rejection

Second-model review of the F49+F50 diff surfaced two real MEDIUM findings
(three others investigated and refuted — no stale-pendingProbe leak, no content
leak, faithful tee mirror). Both fixed TDD-first:

1. F50 gap: runPlan's outer close-fix loop (a full ralph loop judged by the REAL
   close) was still root-less under layerRoot — the same silent-no-op F50 fixes,
   in the plan flow's likeliest fixation site (full menu, command-judged). Wired
   with red-set = the close's own gapKeep (raw close output, so the ^-anchor
   works). New event phase 'fix'; a fix-loop fixation test drives it end-to-end.

2. F49 false-positive class named symmetrically with the false-negative one:
   anchor-disambiguated repeated-record patterns ((?:^- .+$\n?)+) run linearly
   but are flagged by shape. Fail-safe direction (never admits an exponential),
   cost is one redraft. Three REDOS_OVERREJECTED regression tests lock them as
   accepted limitations so a future "smarter" detector can't turn them into
   false negatives (the dangerous direction). Detector unchanged.

559/559, typecheck + build clean. Docs: FINDINGS F49/F50, LAYERS, CHANGELOG, context.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rdicts

Corrects v1.22's premature "steps[] sunset on landing": Layer 2 landed for the
GREEN verdict only; plan-v1 locks soft-green/hitl, which still run only on the
legacy steps[] path (hitl is the only working draft-PR flow). Records that the
green→plan-v1 / soft-green+hitl→legacy split is a build-order artifact, not a
design — plan-v1 is the go-forward home for all three verdicts (the agent
authors the workflow; legacy is operator-hardcoded scaffolding). Honest sunset
gate: legacy retires when plan-v1 admits+implements hitl + soft-green (the
verdict-classes rung, post-Layer-3, with the RSI judged-floor caveat for
rubric), as a rewrite-deletion parked for hamr's explicit go. Notes F50 moved
the last stranded capability (Layer R) onto plan-v1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/code-review medium branch flagged that hasNestedQuantifier missed the
redundant-wrapper class — ((a+))+, (?:(a+))*, (((a+)))+, ((\w+))* — the
same exponential class as (a+)+ with an extra grouping level. The flat-only
scan propagated an inner repeat to the parent only when the group was
DIRECTLY re-quantified, so the wrapped forms validated and could still hang
the untimed exit evaluator (each measured >8s on ~29 chars).

Fix: on a group close, propagate the inner-repeat flag up through the
enclosing group when the group's own body already repeats. MONOTONIC — only
ever sets quant=true, so it can only ADD rejections (the fail-safe direction)
and provably cannot introduce a new false negative (the dangerous one). All
17 REDOS_GOOD and 3 REDOS_OVERREJECTED cases unchanged; +5 wrapper cases in
REDOS_BAD. Full suite 564/564, typecheck, build:types clean.

Docs: CHANGELOG / PRD v1.23 / bareloop.context.md (adopter contract) / FINDINGS
F49 updated to record the wrapper class + the monotonic close. README unchanged
(pitch-only; a detector detail is the wrong genre).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lanrun)

Record the parked decision: the tee wiring (Finding 6/7 settle logic) is
duplicated ~60 lines across the legacy and plan-v1 paths, currently faithful.
Not extracted — legacy is a scheduled rewrite-deletion at the verdict-classes
rung, so the duplication resolves at that deletion; refactoring code slated for
deletion is negative-value and risks reintroducing the blind-instrument class.
Accepted cost: a drift window (both files change together) until legacy retires.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hamr0
hamr0 merged commit 89ce186 into main Jul 23, 2026
2 checks passed
@hamr0
hamr0 deleted the f49-f50-layer-r-planflow branch July 23, 2026 22:12
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