Skip to content

feat(planning): hedged decision kind and unticked-commitment gate on the interview page - #5631

Merged
kyle-sexton merged 9 commits into
mainfrom
feat/5471-hedged-commitment-gate
Sep 30, 2026
Merged

kyle-sexton merged 9 commits into
mainfrom
feat/5471-hedged-commitment-gate

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Closes #5471

Summary

The interview page had no hedged decision kind, and the register gate graded a row clean while its commitments were unticked. This adds the hedged kind end to end and makes an accepted or hedged row with an unticked commitment export as open, so lock (check-open-questions.sh) fails it.

Fix

  • surface/round.py, server.py, schema/*.json: hedged decision with a required condition note.
  • surface/exporters.py: exports answer:: hedged: <recommendation> with the condition in note::; import_ledger reads it back as hedged (legacy free-text: hedged: still imports as own). settle() returns open for an accept or hedge with any unticked commitment, including after revise replaces the commitment list; import accepts that combination.
  • surface/index.html: Hedged choice (key h), hedged rows show commitments under To confirm.
  • Skill text (interview/context/surface.md, loop.md, audit-answers), DEFERRED.md rows 1-2 removed, interview-defenses.test.sh digests re-pinned.
  • planning 0.52.0 to 0.53.0 with CHANGELOG entry.

Verification

  • python3 -m unittest discover -s plugins/planning/surface -p 'test_*.py': 432 tests, OK (1 skipped)
  • bash plugins/planning/tests/interview-defenses.test.sh: PASS=165 FAIL=0
  • bash plugins/planning/scripts/check-open-questions.test.sh: passed
  • scripts/validate-plugins.sh: passed
  • scripts/check-changelog-parity.sh --check, --check-order, --check-bump origin/main: passed
  • Pinned ruff check and format on plugins/planning/surface: passed (T2)

Related

Refs #4653 (owner decision, Option B items 1 and 2). Follows #4611 / #5470. Related: #5452, #5191, #5461.

🤖 Generated with Claude Code

kyle-sexton and others added 6 commits September 30, 2026 16:35
A hedged decision is an accept that carries a required one-line condition. It
flows through the record-terminal op, the page event and responses rebuild, the
four schemas, and the exporters: the ledger writes `answer:: hedged: <rec>` with
the condition in `note::`, import-ledger reads it back as `hedged` (refusing one
with no condition), and the legacy `free-text: hedged: ...` form still imports
as `own`.

Refs #5471

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mitment as open

An accept or hedged decision whose recommendation still has an unconfirmed
commitment now exports as an open register row that keeps its answer, note and
commitments, so `lock` (check-open-questions.sh) no longer passes on a
promise nobody confirmed. Import restores the decision with those commitments
unticked, and the Brief names the gated rows beside the named risks.

Refs #5471

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page offers Hedged as a choice (key h) whose note is the required
condition, posts it as a hedged event, styles it like Accepted, and counts
its commitments in To confirm.

Refs #5471

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the skill text

Refs #5471

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…itment gate

Refs #5471

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review September 30, 2026 21:41
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-30T21:51:28.026269Z a2f238c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 34s —— View job


Reviewing PR #5631

  • Gather context (PR diff, files changed)
  • Run /review:code-review skill
  • Post findings as inline comments
  • Post summary

Summary

Reviewed the hedged decision kind and the unticked-commitment gate against origin/main (head a2f238c). This is a well-scoped, thoroughly tested change — I did not find any high-confidence correctness bugs a careful senior reviewer would block on, so no inline comments were posted.

What I checked closely:

  • settle() gating (exporters.py#L488-L498): an accept/hedged decision with any unconfirmed commitment now settles to open; commitments()/marked_commits() correctly use commitsSinceSeq so confirms against a pre-revise commitment list don't count — matches the PR's claim about surviving a revise.
  • Gate enforcement: confirmed in check-open-questions.sh (open_count + superseded > 0 fails with status=open) that an open-graded row actually fails lock, rather than trusting the PR description's claim.
  • Import/export round-trip (answer_kind, import_named, held_terminal): the hedged:/accepted: prefixes, the required-condition refusal on import, and the legacy free-text: hedged: → own compatibility path are all consistent and covered by round-trip tests (test_a_hedged_answer_exports_its_condition_and_round_trips, test_the_legacy_free_text_hedge_still_imports_as_own).
  • Schema/UI/server consistency: hedged added consistently across event, ops, questions, responses schemas, server.py (LINE_CAP validation + blank-text rejection), round.py (record-terminal --decision hedged), and index.html (choice list, keybinding h, note-required styling, commitments shown under "To confirm"). The ui_c.js test update also fixes a latent event-count assertion (nBeforeReopen) that would have been broken by the new test's extra events.
  • Docs: loop.md/surface.md/audit-answers/SKILL.md updates accurately reflect the new behavior, and the removed "The register gate does not grade parts" sentence in loop.md is correctly dropped since it's no longer true.

Not independently verified (author-claimed only, per lane rules): the PR's cited test run output (432 unittest tests, interview-defenses.test.sh PASS=165, check-open-questions.test.sh, validate-plugins.sh, changelog-parity checks, ruff). Bash tool calls in this environment required interactive approval I don't have in this lane, so I could not execute these myself — reading the diffs is what backs the findings above.

No issues found worth blocking on.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 8s —— View job


Security review

  • Gather context (PR diff, changed files)
  • Check skip gate
  • Review changed files for security-relevant issues
  • Check for instruction-surface deletions (CLAUDE.md/AGENTS.md/skill/rule files)
  • Post findings

No security issues found.

Scope: this PR adds a hedged decision kind and an unticked-commitment gate to the local, stdlib-only interview surface (plugins/planning/surface/{exporters,round,server,index.html} plus schema/tests/docs). Reviewed the full diff against origin/main (commit a2f238c).

Notes from the review:

  • The server remains 127.0.0.1-only, token-guarded stdlib HTTP (plugins/planning/surface/server.py); this PR adds no new endpoints or trust boundaries, only a new decision value plumbed through existing paths.
  • New user-supplied text (hedged's condition) goes through the same rendering and length-cap discipline as existing free-text fields: server-side LINE_CAP enforcement in server.py/round.py (client-side check in index.html is UX-only, not a security control since the server re-validates), and HTML export continues to escape via esc() (e.g. named_risks output in export_report, exporters.py#L858-L866).
  • import_ledger's new hedged parsing (decision_fields, held_terminal, import_named in exporters.py) rejects malformed input via refuse(...) rather than silently coercing it, consistent with the existing ledger-import posture.
  • Schema changes (event.schema.json, ops.schema.json, questions.schema.json, responses.schema.json) are additive enum entries, not relaxations of validation.
  • No CLAUDE.md/AGENTS.md/rules/skill/agent files are touched, so the instruction-surface-deletion lens doesn't apply to this diff.
  • No GitHub Actions workflow changes in this PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2f238c3eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/planning/surface/exporters.py Outdated
kyle-sexton and others added 2 commits September 30, 2026 17:57
…dged answers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 4s —— View job


Security review — PR #5631

  • Gather context (PR diff via gh pr diff 5631, files changed)
  • Invoke /review:security-review skill exactly
  • Check skip gate / instruction-surface-deletion lens
  • Post findings

No security issues found.

Scope: reviewed the full PR diff (gh pr diff 5631, 6 commits) against origin/main at head bbefe7c — the hedged decision kind and the unticked-commitment gate across plugins/planning/surface/{exporters,round,server,index.html}, schemas, and skill docs. bbefe7c itself is a no-op merge of unrelated origin/main history into this branch (gh pr diff correctly excludes it); the only content since the previously-reviewed commit a2f238c is the f7c7421 fix below.

What I checked:

  • The f7c7421 fix for the Codex P2 finding (exporters.py#L1038-L1046): import_named's open-status exception for a decide row with unticked commitments is now gated on answer.startswith(("accepted: ", "hedged: ")), so a hand-edited open row carrying free-text:/alt plus an unticked commitment is refused as contradictory rather than silently reconstructed as own/alt and graded answered on the next export. A regression case was added to test_a_named_row_with_an_unknown_or_contradictory_field_is_refused covering exactly that combination. Confirmed answer is in scope and non-None whenever kind == "decide" (guaranteed by answer_kind), so no AttributeError path.
  • New user text (hedged's condition): server-side LINE_CAP (500) and non-empty checks in server.py (record()) and round.py (op_record_terminal) are enforced independent of the client; index.html's matching LINE_CAP check is UX-only. Rendering continues through esc() everywhere the condition/note reaches HTML output (e.g. export_report's named-risks list), and through the existing ledger note:: escaping for markdown export. No new sink bypasses existing escaping.
  • settle()/import round-trip consistency: confirmed settle() always recomputes a row's status live from q's terminal decision and commitments(q, events) rather than trusting a ledger's literal status: field, so even though import_named doesn't independently re-validate that an answered status's commitments are fully ticked, the next export self-corrects to open — no persistent bypass of the lock gate via a hand-crafted ledger.
  • Schema/server/page consistency: hedged added consistently as an additive enum value (no validation relaxed) across all four schemas, server.py's DECISIONS set, and round.py's DECISIONS tuple.
  • Instruction-surface deletions: the only "removed" prose is loop.md's now-false "The register gate does not grade parts" sentence and two completed DEFERRED.md rows — both are corrections tracking the new stricter gate, not a weakened guardrail. No CLAUDE.md/AGENTS.md/rules/skill-frontmatter files are touched, and no GitHub Actions workflows changed, so the instruction-exception-register lens doesn't surface anything here.

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 0s —— View job


Reviewing PR #5631

  • Gather context (PR diff, files changed)
  • Run /review:code-review skill
  • Post findings as inline comments
  • Post summary

Summary

Ran the CI code-review lane (/review:code-review) against origin/main at head bbefe7c. Read the full diff (plugins/planning/surface/{exporters,round,server,index.html}, schemas, and tests) with particular attention to the new hedged decision kind and the unticked-commitment gate in settle() / import_named() / held_open() / named_risks().

No inline comments posted — I did not find a new high-confidence correctness issue a careful senior reviewer would block on.

What I traced closely:

  • The open-row import exception Codex flagged (exporters.py#L1038-L1045): confirmed this head already carries the fix from f7c7421 — fits["open"]'s third disjunct now requires both kind == "decide" and answer.startswith(("accepted: ", "hedged: ")), so a hand-edited free-text:/alt row with an unticked commitment and status open no longer round-trips as a silently-closed own/alt decision; it's refused as contradictory (covered by the new test_a_named_row_with_an_unknown_or_contradictory_field_is_refused case).
  • settle()'s gate (exporters.py#L488-L498): only accept/hedged decisions flip to open on an unconfirmed commitment; own intentionally stays answered (unchanged, over-counts as a named risk instead) per test_an_own_answer_and_an_alternative_do_not_open_the_row. commitments() uses marked_commits(), which respects commitsSinceSeq, so a stale confirm from before a revise correctly doesn't count — matches test_revised_commitments_reopen_an_accept_whose_earlier_confirms_no_longer_tick.
  • held_open() / named_risks() (exporters.py#L572-L590): correctly excludes a genuinely held (waiting) row from the new gate's "decided but open" reporting, so a row still waiting on research/user isn't double-counted as a named risk before it's actually decided.
  • Hedged note requirement: both decision_fields/settle (export) and import_named (refuse("a hedged answer with no condition in field 'note'", ...)) enforce the condition is always present, consistent in both directions, and round-trip tests (test_a_hedged_answer_exports_its_condition_and_round_trips, test_a_hedged_answer_with_no_condition_is_refused_on_import) cover it.
  • index.html: DEC, LABEL, accepted(), commitRows(), reconfirmOf(), choicesOf(), armKind, noteKind, and the h keybinding are all consistently updated for the new choice; withNote() is correctly left accept-only since a hedged note is the condition itself, not a "note Claude should answer."
  • server.py / round.py: hedged flows through the existing generic DECISIONS handling; the only special-cased validation (condition required, LINE_CAP) is applied identically server-side (server.py) and for the terminal mirror (round.py), and client-side (index.html) checks are UX-only since the server re-validates.

Not independently re-verified here (author-claimed in the PR body): the cited test run output. I read the diff and the new/changed tests rather than executing the suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 8s —— View job


Security review — PR #5631

  • Gather context (PR diff via git diff origin/main...HEAD, files changed)
  • Invoke /review:security-review skill exactly
  • Check skip gate / instruction-surface-deletion lens
  • Post findings

No security issues found — skip gate applies (this head already has a successful security review that still applies).

Diffed origin/main...HEAD at the current head 0260d58 and confirmed it is line-for-line the same 20-file, 469/-87 diff already reviewed and cleared at a2f238c and again at bbefe7c. The only commits added since the last review are:

  • efb9f4b — an unrelated disk-hygiene fix, and
  • 0260d58 — a no-op merge of origin/main into this branch,

neither of which is authored on this PR or touches plugins/planning. Diffing each changed file's content between bbefe7c and 0260d58 shows the apparent deltas in index.html, round.py, test_round.py, and tests/ui_c.js come entirely from origin/main's independent advancement (e.g. the unrelated #5569 fix) landing in both trees via the merge — they cancel out of the three-dot diff, which is what matters for review scope. Specifically re-verified still present at this head:

  • The Codex P2 fix (f7c7421): import_named's open-row exception now requires answer.startswith(("accepted: ", "hedged: ")) (exporters.py#L1038-L1049), so a hand-edited open row carrying free-text:/alt plus an unticked commitment is refused as contradictory rather than silently reconstructed as own/alt and later graded answered.
  • settle()'s gate (exporters.py#L488-L498) and held_open()/named_risks() are unchanged from the prior review.
  • LINE_CAP (500) is still enforced server-side for the hedged condition in both server.py (Hub.record, server.py#L925-L935) and round.py's terminal mirror (round.py#L638-L644); index.html's matching check is UX-only.
  • Schema changes remain additive enum entries only (event, ops, questions, responses), no validation relaxed.
  • No CLAUDE.md/AGENTS.md/rules/skill-frontmatter files are touched, and no GitHub Actions workflows changed, so the instruction-exception-register lens doesn't apply.

No new security-relevant surface was introduced since the last completed review of this content.
· branch feat/5471-hedged-commitment-gate

@claude

claude Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 49s —— View job


Reviewing PR #5631

  • Gather context (PR diff, files changed)
  • Run /review:code-review skill
  • Post findings as inline comments
  • Post summary

Summary

Ran the CI code-review lane (/review:code-review) against origin/main at head 0260d58.

Skip gate applied (criterion 4: this head already has a successful review from this lane that still applies). Confirmed via git diff origin/main...HEAD that this PR's net contribution is still exactly the 20 files / +469/-87 in the PR description, unchanged since the two prior code-review-lane passes on this branch (at a2f238c and bbefe7c, the latter already carrying the f7c7421 fix for Codex's P2 finding on the open-row import exception in exporters.py). The only commits since bbefe7c are efb9f4b (an unrelated disk-hygiene fix from main) and the 0260d58 merge that pulled current main in. git log bbefe7c..HEAD touches none of this PR's own files (exporters.py, server.py, schemas, test_exporters.py, test_server.py, test_schema.py, DEFERRED.md, skill docs, plugin.json, interview-defenses.test.sh) — those are byte-identical to the previously reviewed commit.

round.py, index.html, test_round.py, tests/ui_c.js, surface.md, and CHANGELOG.md do show a diff between bbefe7c and HEAD, but that's main's own independent advancement in those files (e.g. round.py's op_reply now always sets a kind field, unrelated to the hedged-gate work) landing via the merge, not new commits on this branch — it nets to zero in the three-dot diff against current origin/main, so it isn't part of this PR's reviewable surface.

No new findings to post; nothing in this PR's own diff has changed since the prior passes that already cleared it.

@kyle-sexton
kyle-sexton merged commit d77c7a4 into main Sep 30, 2026
20 checks passed
@kyle-sexton
kyle-sexton deleted the feat/5471-hedged-commitment-gate branch September 30, 2026 22:53
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.

feat(planning): hedged decision kind and unticked-commitment gate on the interview page

1 participant