Skip to content

fix(data): every project-data question through the question tool; skills name the repository - #672

Open
mlieberman85 wants to merge 10 commits into
darnitdevorg:mainfrom
mlieberman85:fix/question-picker
Open

mlieberman85 wants to merge 10 commits into
darnitdevorg:mainfrom
mlieberman85:fix/question-picker

Conversation

@mlieberman85

Copy link
Copy Markdown
Contributor

Stacked on #671. Review the last 3 commits: the question selectors, the skills naming the repository, and the flow test.

Summary

Questions. Since #552, _build_ask_user_params returned no selector for free-text questions or for choice questions with more than 4 values. So one batch was split between Claude Code's question tool and plain text. Every question now has a selector:

  • A long choice list shows its first 4 values and names all of them in the question, each typeable under Other (FR-015).
  • A free-text question offers "Skip for now" and "Answer in chat", never a value.

This builds on #657, which already merged the word-boundary headers and the yes/no hint in the question.

Repository naming. The audit and comply skills called the audit without owner and repo. Confirmed project data and stored PASS candidates apply only to a named repository, so they were never applied: after answering the data questions, a re-audit showed the same results (#646). Every skill now:

  • asks the person to confirm the repository, suggesting the origin remote;
  • names it on every call.

Comply re-runs the audit after the data is confirmed. A test checks every skill.

Spec: 7.x and the data procedure. The flow test now checks that every question is in ask_user_batch, with at most 4 per round.

Refs #650, #646, #655.

Type of Change

  • Bug fix (non-breaking change fixing an issue)

Framework Changes Checklist

  • Updated framework spec (docs/architecture/framework-design.md, 7.x)
  • Ran uv run python scripts/validate_sync.py --verbose and it passes

Testing

  • uv run pytest tests/ on the whole rebased stack (18bd63e): 5286 passed, 18 skipped
  • e2e: see the table below. On wtf-frontend, the confirmed answers make QA-02.02, QA-04.01 and QA-04.02 N/A, which they did not when the repository was unnamed.
  • uv run ruff check .

End-to-end

darnit-e2e: headless /darnit:darnit-audit openssf-baseline through Claude Code. Confirm tools are disallowed and the repository is named in the prompt.

darnit darnit repo wtf-frontend cosign-installer
main (2026-10-20; cosign-installer on 603bdda) 387s, 44 turns 415s, 47 turns 297s
#653 only (2026-10-21) 299s, 57 turns 250s, 55 turns 185s, 39 turns
full stack, rebased on 8070949 (2026-10-23) 282s, 50 turns, 30 judgments 145s, 23 turns, 15 judgments 141s, 30 turns
  • Results: per-control results match main. The only differences are controls the model judged, and the effect of operator-side answers on wtf-frontend, which needs the repository named. wtf-frontend also shows QA-03.01 and QA-07.01 passing because their platform settings changed on GitHub.
  • Stored candidates skew the comparison. A control with a stored PASS candidate for unchanged evidence is skipped, so run-to-run times depend on how many are stored. darnit's HEAD moved to 8070949, which invalidated its stored candidates, so it was judged in full (30 controls).
  • Denials:
    • darnit: one page of get_judgment_requests was larger than Claude Code's tool-result limit and was saved to a file. The agent's attempt to read it with Bash was denied. This is the oversized single-request follow-up.
    • wtf-frontend: the agent tried to run zizmor itself, and that was denied.

AI assistance

  • No AI assistance was used
  • AI assistance was used

Claude Code (Claude Opus) wrote the change, tests and this description; I reviewed them and ran the e2e.


Drafted with AI assistance (Claude), reviewed and filed by me. Measurements quoted were executed and verified.

Marc-cn
Marc-cn previously approved these changes Oct 29, 2026

@Marc-cn Marc-cn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed its own three commits. Every question gets a selector, long choice lists show four and point to Other, free text offers Skip / Answer in chat, and the skills name the repository on every call (the #646 fix). Two things to check, not blockers: (1) for a choice with more than 4 values the code adds 'Choose Other to type a value not listed below' but does not name values 5+ in the question; fine if prompt already lists them, otherwise they are visible only in allowed_values. (2) the free-text value_map maps Skip and Answer-in-chat to the strings "SKIP" and "ASK_USER_FOR_VALUE"; an agent applying answer_mapping mechanically could pass "SKIP" as the answer. null, or leaving those labels out of value_map, would be safer. Good to merge in order.

@Marc-cn Marc-cn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-read the head of fix/question-picker, own commits only. Routing every question through the question tool is the right fix for #655, and the enum vocabulary check keeps "type it under Other" safe for choice keys. One hole it opens, and two doc/skill inconsistencies:

  1. The two labels added to every free-text key, "Skip for now" and "Answer in chat" (tools.py:1058-1062), and the value_map sentinels "SKIP" / "ASK_USER_FOR_VALUE" (tools.py:872), are accepted by the server as confirmed values: context_writes.py:189-193 refuses only None and out-of-vocabulary enum values, and free-text keys have no vocabulary. If the agent forwards the chosen label — which is what AskUserQuestion returns — confirm_project_data(security_contact="Skip for now", ...) is recorded as a person-confirmed value (maintainers becomes ["Skip for now"]). Procedure prose is the only guard, on a server whose premise is that the agent is untrusted. Please refuse those four strings (and inside list values) in record_value_confirmation with a clear reason, define them once in darnit.server.procedures so core and baseline share them, and add a test. (Follows up the value_map note in my first review.)
  2. specs/042-candidate-integrity/contracts/context-confirmation-tools.md:27 still says selector options are omitted above 4; framework-design 1748 (changed here) says the opposite and names that contract as the definition. The sentinels and the free-text selector should be documented there too.
  3. darnit-comply/SKILL.md:42 now says to call the audit again after confirming data; line 85 still says "Do NOT run a separate audit before calling remediate_audit_findings". Reword 85.

Not blocking: "name the repository on every call" is enforced only by skill text plus a test that inspects two lines (darnit-remediate:110 already names none); a warnings entry from get_pending_data / the audit when target is None would make #655 a real fix. The agent-flow test takes answers from the ANSWERS dict, not from ask_user_batch / answer_mapping, so it can't fail on point 1.

get_pending_data and get_judgment_requests carry a 'procedure': the
ordered steps for the result, with calls that name only the repository
the caller named. A confirm_project_data result on a server with
get_pending_data ends with the next get_pending_data call. The text has
one home in the framework, so skills and clients defer to it.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
darnit-comply told the agent to "follow the /darnit-data skill" and
"follow the /darnit-audit skill", but nothing loads another skill's
text, so the comply run improvised data collection from one line and
asked questions differently from a darnit-data run.

darnit.server.procedures holds the steps once. get_pending_data returns
them as 'procedure' (show candidates labelled unconfirmed, ask every
question, per-question skip, one confirm_project_data call with
accept_candidates filled only after a yes, the next call, the review of
stored values, the summary), get_judgment_requests returns the judging
steps on every page, and the baseline confirm_project_data result ends
with the next get_pending_data call. Calls name only the owner and repo
the caller passed, never ones read from the checkout's remote, and hold
no candidate value.

The skills defer to the procedures instead of restating them, and no
skill tells the agent to follow another one (a test checks). The comply
and remediate skills pass owner and repo to remediate_audit_findings,
which needs them to find operator-side confirmations (darnitdevorg#612).

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
A page now names, in 'skipped', the controls left out because they
already have a stored PASS candidate, and the judging procedure says
not to request them again. In the darnitdevorg#644 e2e runs the agent called the
tool again with those ids and got empty pages back.

Refs darnitdevorg#644.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
…l_ids

Section 7.10 and the 042 contract: get_pending_data takes no host, so the
confirm_project_data call in its procedure names host as a placeholder to
fill for a repository not on github.com. Answers are mapped by
answer_mapping, where a No on a candidate, or Other, is the value the
person typed. The next get_pending_data call keeps the caller's
control_ids. The contract now lists procedure.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
Review of darnitdevorg#671. The procedure that replaced the old directive lost two
of its instructions:

- Step 4 built the confirm_project_data call without host, so on a
  repository not hosted on github.com an agent following it verbatim
  recorded confirmations under github.com. get_pending_data takes no
  host, so step 4 now names it as a placeholder.
- Step 4 said only that Yes/No questions take true/false. A candidate
  question is also Yes/No, so a No could be sent as false. Step 4 now
  maps answers by answer_mapping: a No on a candidate, or Other, is the
  value the person typed.

Step 5 and the next step after confirm_project_data keep the caller's
control_ids, so a scoped collection stays scoped. The ASCII test
checked json.dumps output, which is always ASCII; it now checks the
text itself.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
Since darnitdevorg#552, _build_ask_user_params returned no selector for free-text
questions or for choice questions with more than 4 values, so a batch was
split between Claude Code's question tool and plain text. Every question
now has a selector: a long choice list shows its first 4 values and names
all of them in the question (each typeable under Other, FR-015), and a
free-text question offers "Skip for now" and "Answer in chat", never a
value. Word-boundary headers and the yes/no hint in the question came
from darnitdevorg#657 and are kept as merged. The spec (7.x) and the data procedure say
the same.

Refs darnitdevorg#650, darnitdevorg#655.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
The audit and comply skills called the audit without owner and repo, so
confirmed project data and stored PASS candidates, which apply only to a
named repository, were never applied: after answering the data questions a
re-audit showed the same results, and submit_judgment (which requires the
name) saw a different set of PENDING controls than the audit. Every skill
now asks the person to confirm the repository, suggesting the origin
remote, and names it on every call; comply re-runs the audit after data is
confirmed. A test checks every skill.

Refs darnitdevorg#646, darnitdevorg#655.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
Refs darnitdevorg#655.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
Section 7.10 and the 042 contract: confirm_project_data refuses "Skip
for now", "Answer in chat", "SKIP" and "ASK_USER_FOR_VALUE" as a value,
alone or inside a list, since an agent may forward the label its
question tool returned. The contract no longer says a selector is
omitted above 4 values: every question has one, a long choice list
offers its first 4 and names them all, and a free-text question offers
the two labels above, never a value.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
Review of darnitdevorg#672. Every free-text question now offers "Skip for now" and
"Answer in chat", and answer_mapping maps them to "SKIP" and
"ASK_USER_FOR_VALUE". record_value_confirmation refused only None and
out-of-vocabulary enum values, so an agent that forwarded the label
AskUserQuestion returned recorded it as a person-confirmed value, for
example maintainers = ["Skip for now"]. Only the procedure text guarded
against that.

record_value_confirmation now refuses those four strings, alone or in a
list, before and after coercion, with a reason that says to leave the
key out or ask for the value. They are defined once, in
darnit.server.procedures, and the selector, answer_mapping and the
procedure use them. The agent-flow test forwards a "Skip for now" label
and requires the refusal.

darnit-comply said both to re-run the audit after confirming data and
"Do NOT run a separate audit"; the gotcha now says only not to audit
just to feed remediate_audit_findings.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Michael Lieberman <mlieberman85@gmail.com>
@mlieberman85

Copy link
Copy Markdown
Contributor Author

Thanks, good catch on point 1. All three points are fixed in two new commits on top: "docs(spec): a selector label or answer_mapping marker is not an answer" and "fix(data): refuse a selector label or marker as a confirmed value". Rebasing on the updated #671 conflicted in procedures.py and framework-design 7.10, where both PRs edit the same step and paragraph. I resolved it by keeping both sets of changes in the first commit; the other two commits are unchanged.

  1. record_value_confirmation refuses "Skip for now", "Answer in chat", "SKIP" and "ASK_USER_FOR_VALUE", alone or inside a list, both before and after coercion. The reason it gives is that the value is a question option or answer_mapping marker, not an answer, and to leave the key out or ask for the value. The four strings are defined once in darnit.server.procedures (NOT_AN_ANSWER), and the selector, answer_mapping and the procedure use them. Tests:

    • every string, as a string and inside a list, for security_contact and maintainers;
    • the operator-side path;
    • a value that only contains "SKIP" is still accepted.

    The agent-flow test now forwards the "Skip for now" label for a free-text question over MCP and requires refused. With the check disabled, all 14 new checks fail.

  2. The 042 contract no longer says selectors are omitted above 4 values. It documents the free-text selector, the two markers, the candidate "No" marker, and the refusal. Framework-design 7.10 states the refusal too.

  3. The comply gotcha now reads "Do NOT run an audit just to feed remediate_audit_findings: it audits internally. The one audit after collecting data (step 4) is for showing the person what changed."

Not done, from your non-blocking notes: a warning from get_pending_data or the audit when no repository is named. That changes what those tools return, so I'd rather do it in its own PR.

The headless comply run on #693's head, which includes this branch, is in my reply on #693.


Drafted with AI assistance (Claude), reviewed and filed by me. Measurements quoted were executed and verified.

@Marc-cn Marc-cn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved on c77cb47. The server now refuses 'Skip for now', 'Answer in chat', 'SKIP' and 'ASK_USER_FOR_VALUE' as a confirmed value, alone or in a list, before and after coercion and on the operator path, with the strings defined once in darnit.server.procedures and used by the selector, answer_mapping and the procedure; the agent-flow test forwards the label over MCP and requires refused. The 042 contract documents the free-text selector and the markers, and darnit-comply line 85 now says what the one extra audit is for. On the VM against main: tests/darnit/server + darnit_baseline + config + the MCP integration suite 1983 passed, ruff and validate_sync clean.

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.

2 participants