fix(detect --stories): gate stories on per-criterion verdicts, not model silence - #2390
fix(detect --stories): gate stories on per-criterion verdicts, not model silence#2390agarwal-ishaan wants to merge 22 commits into
Conversation
Live-model validationThe PR body noted this had only been exercised with stubbed evaluators. It has now been run end to end against a real provider, deliberately at the weak-model condition from the issue: 1. Positive — a correct prompt set no longer fails on a weak model Story All four carried verbatim citations, and none was flagged 2. Negative control — the gate still discriminates A gate that only ever says PASS would be no better than the one it replaces. I injected a fifth criterion the prompt demonstrably does not satisfy ("sends an SMS notification to the on-call engineer ... and waits for an acknowledgement code"): Verdict flipped to FAIL, exit non-zero, and AC1–AC4 stayed SATISFIED — the failure is localized to the criterion that broke rather than smeared across prose. Cost $0.0036. 3. Machine-readable path
Confirming the compatibility claim in the PR body: unsatisfied criteria land in the existing Total spend for all three runs: $0.011. The known-red CI (the two unregistered prompt units) is unaffected by this and still needs the registration described above. |
Controlled comparison: old gate vs new gateRather than assert this is better, I ran both gates over the same story, contract and prompt, varying only (a) the model strength and (b) whether the prompt contains a real regression. Setup. Story Results
Total spend across all runs: $0.63. 1. The legacy gate has no discriminating power at any strengthIt returned FAIL in every condition — including on correct prompt sets, including at strength 0.9. Its FAIL on a correct prompt set is indistinguishable from its FAIL on a regressed one, so the verdict carries no information about the prompts. This is worth stating plainly because it is stronger than what #2389 claims. The issue describes "weak model fails, strong model passes"; in this experiment the legacy gate never passed anything at any strength. Pinning a floor strength — the issue's fallback proposal — would not have fixed it. 2. Why the legacy gate fails correct promptsStructural, not incidental. It hedges once (line 35: "Some prompts maybe unaffected ... or already have the changes applied"), but that is a single clause inside a task whose output format has no no-change branch. Compounding it: in story mode the "change description" is the story + contract. The model is handed a specification and asked how it would edit the prompts to apply it. Restating a contract inside a prompt is always a plausible edit, even when the prompt already satisfies it — so the question is close to unanswerable with "nothing". 3. Two honest caveats(a) A false negative at the weakest tier. At strength 0.2 the regressed set passed, reproducibly (4/4). The model returned UNCLEAR on AC3 — an honest "I cannot decide" — and this PR maps I intend to fix this by using the outcome the schema already has, unless reviewers prefer otherwise:
(b) This PR gates on ReproducingStrengths 0.5, 0.7 and 0.9 all resolved to |
|
Two scope updates on this PR. Reduced to the engine swap alone. An earlier revision added a Where verification belongs in the command tree is a design question that can be argued without blocking the fix. Strength default split out to #2394. While measuring this gate I found that story validation hardcodes That is a pre-existing default, not something this PR introduces, and fixing it changes cost characteristics for every caller — so it is tracked separately rather than folded in here. Net effect: this PR now changes the engine and nothing else. |
There was a problem hiding this comment.
Requesting changes. This PR solves a real structural problem: detect_change is a propagation planner, so an empty suggestion list is not a sound story oracle. Per-criterion classification is the right direction, and the new code, prompts, architecture entry, and managed-unit registrations are present. The current head is not safe or repository-accepted yet.
-
P1 — the default command uses the one tier proven to pass a real regression. pdd/commands/analysis.py:664 and pdd/user_story_tests.py:1793 select strength 0.2. The live negative control recorded here and in #2394 passed the broken prompt set 5/5 at that default, confidently as satisfied. Plain pdd detect --stories can therefore return exit 0 for the known regression. At minimum land the stronger default before this engine becomes active and ensure below-floor results cannot look silently trustworthy.
-
P1 — criteria-mode link caching drops prompts that satisfied other criteria. At pdd/user_story_tests.py:2012-2018, missing metadata is derived from changes_list, but that list now contains only unsatisfied verdicts. Reproduction: evaluate A+B, let AC1 be satisfied by A and AC2 fail in B; the first run writes only b_python.prompt into pdd-story-prompts, and the next run never evaluates A. Preserve the full evaluated scope in criteria mode, or derive links from every verdict with a full-scope fallback.
-
P1 — protected rollout policy is red. tests/test_sync_core_pdd_rollout_policy.py failed on the 471-vs-469 managed-unit count, verification-profile requirement mismatch for the modified user_story_tests_python.prompt, and invalid protected inputs. Install the required verification profiles/rotations and separate current versus frozen historical inventory expectations. The branch also conflicts with current main in pdd/sync_core/manifest.py; resolution must preserve the current 8ff5fb..., PR b23599..., and protected historical 558910... authorized pairs as applicable, then rerun the policy suite.
-
P2 — duplicate basenames make repair targeting ambiguous. pdd/story_criteria.py:432 sends only path.name; pdd/user_story_tests.py:146 overwrites duplicate basename keys. A two-directory same_python.prompt reproduction resolved the model response to the last file. The repository already has duplicate auth_python.prompt, cli_python.prompt, and gate_python.prompt names. Send unique relative identifiers and reject ambiguous basename-only targets.
-
P2 — a partially unreadable scope can still pass. pdd/story_criteria.py:511-526 downgrades only unsatisfied verdicts when a linked file was unreadable. With one readable and one invalid-UTF-8 linked prompt plus a satisfied assessment from the readable file, the result was passed=True and verified=True. Any unreadable linked input must make the evaluation incomplete because the model did not evaluate the declared scope; add the satisfied-response regression case.
-
P2 — architecture metadata is incomplete/stale. architecture.json:10111-10113 omits the public guards parameter, the function list omits public parse_non_oracle_guards, and architecture.json:10135 still says story validation runs via detect_change. Align it with pdd/prompts/story_criteria_python.prompt:74-83 and the generated module.
Validation:
- 223 passed across criteria, story orchestration, stable result schema, and analysis CLI.
- 460 passed across caller/change/drift/orchestration/manifest suites.
- Rollout policy established 3 failures after 23 passes.
- git diff --check passed.
- Targeted pylint is not clean on the new evaluator and expanded fix path.
The contributor did useful live positive, negative, and JSON-schema checks, but the E2E evidence is not sufficient for merge because its default-tier negative control fails and the protected repository gate is red. No contributor branch files were modified.
gltanaka
left a comment
There was a problem hiding this comment.
Follow-up review of 00d9e1d: the new commit correctly fixes four earlier findings—unique prompt identifiers/ambiguous basename rejection, partial unreadable-scope handling, architecture metadata, and full-scope caching after a completed criteria evaluation. The remediation suite is green, but this is still not mergeable.
-
P1 — incomplete criteria evaluation can still narrow cached scope. At pdd/user_story_tests.py:2035-2048, full scope is preserved only when evaluation is not None. If evaluate_acceptance_criteria raises StoryCriteriaError, criteria_error is recorded but the caching branch falls back to _select_story_prompt_links. Reproduction with prompts A+B and story prose mentioning only A: the current run is correctly UNKNOWN, but metadata is rewritten to only a_python.prompt; the retry no longer evaluates B. When parsed criteria exist, cache story_prompt_files regardless of evaluator success, or do not mutate metadata on incomplete runs. Add this evaluator-error regression.
-
P1 — protected rollout remains red. The exact inventory test still fails at tests/test_sync_core_pdd_rollout_policy.py:525 because the PR registers 471 managed units while EXPECTED_MANAGED_UNITS at line 94 remains 469. Complete the current-vs-historical inventory transition and run the full rollout-policy suite.
-
P1 — the branch still conflicts with current main in pdd/sync_core/manifest.py. The auto-merged .pdd/sync-ownership.json bytes hash to 4539337c69291dfcab7bf6913183a39fb61a96553ad3c9ee6751b737f03acf18, so choosing either side's existing current digest would be stale. Re-pin the merged current pair while retaining required frozen historical pairs, then rerun rollout gates.
-
P1 — the default trust issue remains operationally unresolved. pdd/commands/analysis.py:664 and pdd/user_story_tests.py:1816 still use strength 0.2, the tier shown in #2394 to confidently pass the known broken prompt set 5/5. The separate fix in PR #2398 is still open, Changes Requested, and has a failing unit-test check. Do not activate this gate in a merge until the stronger default/floor is integrated and green.
Validation on the updated head:
- 227 passed: criteria, user-story orchestration, stable result schema, analysis CLI.
- 313 passed: change, drift, and agentic-change callers.
- Focused rollout: 3 passed, exact inventory failed 471 vs 469.
- Independent delta review reproduced the new evaluator-error caching hole.
- git diff --check passed.
- GitHub reports mergeable=CONFLICTING.
So the design still makes sense, and the update is meaningful, but merging it now would leave a known partial-scope false-pass path, a red protected gate, an unresolved semantic conflict, and a default-tier false negative.
…del silence
`run_user_story_tests` passed a story only when `detect_change` returned an
empty changes list. `detect_change` asks an open-ended "what would you change?",
so the pass bar was "the model volunteered nothing" -- a question LLMs are
strongly biased to answer with something. The verdict tracked model strength
rather than the prompts: the same story, contract, and prompts flipped FAIL to
PASS on `--strength` alone, and a weak model failed a correct prompt set with
suggestions that were already satisfied verbatim.
Story validation now asks a bounded question instead. Every acceptance
criterion is classified exactly once as satisfied/unsatisfied/unclear, and a
satisfied verdict must quote the prompt text that earns it.
- only `unsatisfied` fails a story; `unclear` is emitted as a warning so a
hedging model cannot fail correct prompts
- a criterion left unjudged, or an evaluator error, marks the run incomplete
rather than passing it -- the legacy gate read silence as success, and does
not fall back to `detect_change`, whose empty answer would do the same
- criteria come from the story contract, else the story itself; a story with
no parseable criteria keeps the legacy gate
- `--legacy-detect` / `legacy_detect=True` forces the old gate
- non-quiet output reports each criterion and names the judging model, so a
cheap-model PASS is not mistaken for a strong one
`detect_change` itself is untouched and still owns change propagation and
`pdd fix`. Unsatisfied criteria are rendered into the existing
`{prompt_name, change_instructions}` rows, so `pdd fix`, story-link caching,
and the `pdd.detect.stories.v1` document need no changes -- `unclear` lands in
the existing advisory warnings channel, so the JSON schema is unchanged.
Fixes promptdriven#2389
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New tracked files need explicit entries or the sync manifest reports them as
unaccounted:
- pdd/prompts/story_criteria_{LLM,python}.prompt -> expected-managed units
- tests/test_story_criteria.py -> sync-ownership rule
pdd/story_criteria.py needs no rule: its architecture.json entry makes it a
prompt-backed managed output.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Models often return criterion_id as "AC1: Given a valid CSV..." rather than a bare "AC1". The end-anchored pattern dropped those, turning a real verdict into a false 'unevaluated' and marking a complete run incomplete. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adding the tests/test_story_criteria.py ownership rule changes the sha256 of .pdd/sync-ownership.json, which _sync_rollout_repair_rules pins. Without the re-pin the bridge falls through to base_rules and eight .pdd/meta paths silently lose their repaired ownership. The previously authorized head is kept alongside the new one: tests drive the bridge with different head refs, some frozen historically. Both pairs bind the same reviewed base. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Editing an existing pdd/prompts/*.prompt invalidates its CONTRACT-SHA256 requirement id, which surfaces as 'profile requirements do not match immutable prompt requirements'. docs/ci.md requires prompt transitions to be installed and consumed in two separate merged changes, so this spec update belongs in a follow-up PR. The behavior ships in pdd/user_story_tests.py and the tests exercise that artifact, so reverting the spec text costs nothing functionally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured on a live weak model (gemini-3-flash-preview at --strength 0.2), a genuinely regressed prompt set PASSED, reproducibly 4 runs out of 4. The model had returned `unclear` on the criterion the regression broke -- an honest "I cannot decide" -- and this gate mapped `unclear` to advisory, so the verdict came out green. That is the same fail-open shape the gate was written to remove, just relocated: from "the model said nothing" to "the model could not decide". README's documented contract is explicit that exit 0 means every scoped story "explicitly passed", and it already reserves exit 3 for an incomplete evaluation, with UNKNOWN as a first-class per-story verdict. So this is conformance with the documented behaviour rather than a new state: - any `unsatisfied` -> FAIL - no `unsatisfied`, some `unclear` -> INCOMPLETE (exit 3), story not verified - all `satisfied` -> PASS `unclear` still never FAILS a story. Letting it fail would reimport exactly the model-strength sensitivity this gate exists to remove -- a hedging model must not be able to fail a correct prompt set. It simply no longer claims success. Verified live: the correct prompt set stayed PASS at every strength tested (0.2/0.5/0.7/0.9), so this costs nothing on healthy prompts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…at owns it Gating acceptance criteria alone passes a story it should not. Measured on `story__pdd_bug`: the prompts satisfy all four acceptance criteria, so an AC-only gate returns PASS -- while the same contract's negative cases and oracle details go unchecked. Deleting the `--clean-restart` constraints leaves AC-only reporting a single failure, where the full check reports five (AC3, NC1, NC2, NC3, OR3), each traceable to the deleted text. All three checkable families are now criteria, and the fourth section becomes a constraint on the judge rather than something judged: ## Acceptance Criteria -> AC<n> do the prompts REQUIRE this? ## Negative Cases -> NC<n> do the prompts PREVENT this? ## Oracle -> OR<n> do the prompts DETERMINE this detail? ## Non-Oracle -> guards never grounds for unsatisfied Non-Oracle as a prohibition is the point: the contract already names what it considers irrelevant -- wording, private helper names, styling -- so the judge is constrained by the story's own declaration instead of by generic "do not nitpick" instructions. That attacks the legacy detector's noise structurally. A satisfied citation must now quote text that REQUIRES something. Measured failure it closes: a wrong `satisfied` cited "Options: --manual, --output, --language, ..." -- verbatim, so the existing check passed, but an option list does not require rejecting an option. Adds `pdd story verify`, beside `story add`/`link`/`list`. Verification is not a mode of the change detector: `detect --stories` carries its own schema, verdicts, exit codes and read-only semantics, but lived under a command named for a different job. It delegates to the same implementation, so there is one engine and two front doors; `detect --stories` keeps working unchanged. Verified live at --strength 0.5: correct prompt set PASS with all 13 criteria satisfied; regressed set FAIL naming the five that broke. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tector Where a story has a contract, that contract IS the specification. Asking `detect_change` "what would you change?" re-derived, less precisely, what the contract already states -- and the answer was discarded: `change_instructions` is read in exactly one place in this module (`_format_story_change`, for printing), while `change_main` receives the story+contract oracle. The detect step spent 2 LLM calls to produce a plan nobody read, keeping only prompt names. Repairs are now planned from the same bounded verification that judges them, so each edit traces to a named unsatisfied criterion and the story-fix path drops from 3 LLM calls to 1. Also closes the last instance of the original fail-open. Previously an empty change list returned "No prompt changes needed" and short-circuited before the final validation, so a silent or malformed detector reported success having changed nothing and checked nothing. An empty change list is now only good news when every criterion was actually judged; an undecided evaluation reports that the story could not be verified instead of claiming it is clean. `detect_change` is unchanged and still used here for stories with no parseable criteria, and via `legacy_detect`. It remains the engine for `pdd change`, standard `detect`, PRD propagation, and story prompt-link discovery -- which asks "which prompts is this story about?", a question verification cannot answer because it must be told which prompts to check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
README and the story guide described `detect --stories` as the only spelling and did not say what verification actually checks. They now document the four contract sections (AC require / NC prevent / OR determine, Non-Oracle as a guard), the citation requirement, and that unclear leaves a story UNKNOWN rather than passed. `detect --stories` is documented as the older spelling and kept in the CI/scope-manifest examples, which callers already use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1. HIGH -- `pdd story verify` exited 0 on a story FAIL. `verify_stories`
discarded the result of `ctx.invoke`, but a plain FAIL is signalled by
RETURNING the payload to the group's result callback; `detect`'s own
`raise Exit(1)` is guarded on `ctx.parent is None` and cannot fire through
an invoke. Reproduced (verify exit=0 vs detect exit=1), fixed by returning
the payload, and pinned by a test asserting the two front doors agree.
2. Undecided criteria were reported to automation as a broken prompt link. The
incomplete reason contained the word "prompt", and story_detection_result
classifies row errors by substring, so an undecided story emitted
`prompt:UNRESOLVED_LINK` ("A linked prompt could not be resolved") and sent
operators hunting for a reference that does not exist. Reworded, with a test
asserting the reason never contains "prompt".
3. The generating prompt contradicted the module it generates -- it still said
`unclear` MUST NOT affect the verdict, and knew nothing of negative cases,
oracle criteria, guards, or `verified`. In this repo the prompt is the
regeneration source of truth, so `make generate` would have restored the
fail-open this PR removes.
4. Conflicting duplicate assessments kept the first, so a model that corrected
itself from `satisfied` to `unsatisfied` still passed. Now takes the worst
status, matching every other ambiguity in the module.
5. Unreadable linked prompts were dropped silently and read as missing
requirements: criteria they satisfy came back `unsatisfied`, failing the
story and pointing the repair at other prompts. Those verdicts now degrade to
`unclear` naming the file, and no readable file at all raises.
6. The terminal headline judged FAIL before UNEVALUATED while the caller judged
the reverse, so one run could print FAIL and emit UNKNOWN in the v1 document.
7. `run_user_story_fix(legacy_detect=True)` planned with the legacy detector but
re-validated with the criteria gate.
Not addressed: model-supplied `prompt_name` is resolved against every prompt in
the tree rather than the story's linked set, so a wrong guess could edit an
unlinked prompt. The correct fix is to scope the repair to the linked prompts
the gate evaluates -- a behaviour change worth doing deliberately rather than
folded into this commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The engine swap is what fixes promptdriven#2389: story validation asked a question with no passing answer, and now asks one with three bounded answers that requires evidence. Where verification *belongs* in the command tree is a separate design opinion, and bundling it made this PR carry a new command, a new front door, its own tests, an ownership rule and a digest re-pin for no gain to the fix. Removed: the `story verify` subcommand, its test file, its sync-ownership rule, and the digest re-pin (which returns exactly to b23599ec, the value pinned for the story_criteria test rule). Docs revert to the `pdd detect --stories` spelling while keeping the substantive additions -- the four contract sections, the citation requirement and the unclear/UNKNOWN semantics all describe the engine, which stays. `pdd detect --stories` keeps the sound engine and still exits 1 on a story FAIL. `detect_change` remains untouched for its two non-story callers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9a76c6e to
3ba082f
Compare
gltanaka
left a comment
There was a problem hiding this comment.
Follow-up review of b09c7f82: the update correctly fixes the evaluator-error scope narrowing, the merge conflict, and current profile reconciliation. The core per-criterion design remains worthwhile, but this head is still not safe or repository-accepted.
-
P1 — generated prompt and architecture artifacts disagree, failing both required CI jobs.
architecture.json:10141-10146declaresstory_criteria_python.promptas a dependency ofuser_story_tests_python.prompt, butpdd/prompts/user_story_tests_python.prompt:26-29has no matching<pdd-dependency>story_criteria_python.prompt</pdd-dependency>. I reproduced this withpdd checkup --validate-arch-includes. Unit job 94651404194 and Public CLI Regression job 94651404220 fail on this exact mismatch. Add the dependency and re-rotate the resulting prompt/profile hashes. -
P1 — the rollout ownership bridge is pinned to stale bytes. Commit
b09c7f82removedpreauthorize_absent, so the current.pdd/sync-ownership.jsonSHA256 is1402941794d1b593847eb8c277c641f40ce159c1cde3012d6880400be3763c69.pdd/sync_core/manifest.py:302-305authorizes only the earlierb23599ec...story-criteria head. Consequentlytests/test_sync_core_pdd_rollout_policy.py:2364fails because all eight repaired metadata rules disappear. Authorize the exact new current pair while retaining required frozen historical pairs. -
P1 — historical rollout tests use the current inventory count.
EXPECTED_MANAGED_UNITSis now 471, but the pinned #1875 and replay histories contain 469. Assertions attests/test_sync_core_pdd_rollout_policy.py:2205and:2225fail. Keep 471 for current-head checks and use a frozen 469 constant for those historical states. -
P1 — merging #2390 alone still activates a known false-PASS default.
pdd/commands/analysis.py:664andpdd/user_story_tests.py:1816still default to strength 0.2. The contributor's live #2394 evidence shows a genuinely regressed prompt set passing 5/5 at that exact default with confidentsatisfiedverdicts, so UNKNOWN handling does not protect this path. PR #2398 is now CI-green but remains open/unapproved and overlaps this prompt/profile transition. Validate an ordered stacked/combined head and land the equivalent strength correction with this gate; #2390 is unsafe standalone.
Validation on this head:
- 228 passed: story criteria, user-story orchestration, stable result, and analysis CLI.
- 313 passed: change, drift, and agentic-change callers.
- 81 verification-profile tests and 34 manifest tests passed independently.
- Three exact rollout-policy failures reproduced as described.
git diff --checkclean; merge-tree clean; GitHub reports MERGEABLE.
The contributor's E2E work is useful, but it is not sufficient for merge while its own default-tier negative control false-passes and required repository gates are red.
- Declare story_criteria_python.prompt as a dependency of user_story_tests_python.prompt (matching architecture.json) and rotate the resulting prompt/profile requirement hashes so pdd checkup --validate-arch-includes passes. - Re-pin the sync-rollout ownership repair bridge to the current .pdd/sync-ownership.json bytes (after b09c7f8 dropped preauthorize_absent), while keeping the earlier story-criteria pair as frozen protected history. - Freeze EXPECTED_MANAGED_UNITS at 469 for the promptdriven#1875 protected-history and replay tests, which predate the story-criteria unit registrations that bumped the current count to 471. - Use DEFAULT_STRENGTH instead of a hardcoded 0.2 for story validation's strength default (pdd/commands/analysis.py and the five story-related signatures in pdd/user_story_tests.py), closing the known false-PASS default path documented in promptdriven#2394/promptdriven#2398. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
gltanaka
left a comment
There was a problem hiding this comment.
Follow-up review of 03a3adce: this update correctly fixes the prompt/architecture dependency mismatch and the frozen 469-unit historical assertions, and it repairs the ownership bridge for the isolated PR head. It also raises several direct helper defaults. The PR is still not safe or repository-accepted.
-
P1 — the actual protected-base rollout remains invalid. Building the production manifest for
0faf7be4..HEADreportstests/test_story_criteria.py: tracked path has no ownership rule; the candidate-only ordinary row in.pdd/sync-ownership.jsoncannot grant protected absent-path authority. Loading profiles then raisescandidate active requirement transition rules are ambiguous. The policy has two activeuser_story_tests_python.promptrows around.pdd/verification-profile-rotations.json:650-670; the prior1c467... -> 4a7ccc...row was not retired in favor of the new4a7ccc... -> a12545...row. The focused rollout set is 4 passed / 1 failed attests/test_sync_core_pdd_rollout_policy.py:2706. Install ownership and transition authority through the required protected Phase A, consume it in a later Phase B, and add the real protected-base-to-candidate loader test. -
P1 — the known strength-0.2 false-PASS path is only partially fixed.
pdd/commands/fix.py:216still forces 0.2 into initial and post-fix story validation. Strict agentic-change validation still receives 0.2 frompdd/agentic_change_orchestrator.py:3767and forwards it torun_user_story_testsat line 1549.pdd/commands/generate.py:863,906andpdd/prompts/agentic_change_orchestrator_python.prompt:509also preserve 0.2. Current generate tests explicitly assert that stale default, whileuser_story_tests_python.promptdoes not specify the newDEFAULT_STRENGTHcontract. Update every validation caller and the corresponding prompt/architecture sources; otherwise the contributor's documented 5/5 false PASS remains reachable. -
P1 — current-main integration is still conflicting and the merged ownership digest is stale.
git merge-tree --write-tree origin/main HEADconflicts in.pdd/verification-profile-rotations.json,pdd/sync_core/verification.py, andtests/test_sync_core_pdd_rollout_policy.py. The auto-merged ownership bytes hash to1e26e347..., while the merged manifest authorizes main's2b4cee..., priorb23599..., and branch-head140294...—not the actual merged result. Rebase, preserve current-main rollout semantics, and pin/test the exact resolved bytes.
Validation on this head:
- Architecture include validation passed.
- 228 story/analysis tests passed.
- 342 affected caller tests passed.
- Focused rollout: 4 passed, 1 failed as above.
- Actual
0faf7be4..HEADproduction loader reproduced unowned test + ambiguous active transition. git diff --checkclean; worktree clean.- GitHub reports CONFLICTING and Changes Requested; required CI has not run on this head.
#2398 now contains more complete caller/prompt propagation, but remains Changes Requested with a red unit job; it is not a validated substitute.
…criteria-gate Resolve the story-criteria rotation onto main's post-promptdriven#2380 profile head: the user-story requirement transition now runs 5b1353 -> 103527 and retires main's consumed 1c4670 -> 5b1353 row, so exactly one active row remains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- `tests/test_story_criteria.py` regains `preauthorize_absent`, without which the protected base cannot own a path this branch adds; re-pin the ownership bridge to the resulting bytes. - `pdd fix`, `pdd test` and the agentic change orchestrator stop forcing strength 0.2 into story validation; they now default to DEFAULT_STRENGTH. - State the DEFAULT_STRENGTH contract in user_story_tests_python.prompt and re-mint the profile/rotation chain it heads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…essor PR promptdriven#2374 already established the single-change shape for this: retire the consumed successor row and consume its replacement in the same hash-bound transition. Do the same for the user-story row. - `.pdd/verification-profile-rotations.json` retires main's consumed `1c4670 -> 5b1353` row in favour of `5b1353 -> 51fd30`, so exactly one active row remains for the identity. - `_STORY_CRITERIA_ROTATION_POLICY_BYTES` / `_PROFILE_BYTES` bind the pair to exact whole-file bytes, and `exact_pr2390_story_criteria_successor` lapses on any other edit. - Register the two new story_criteria prompts in `_BOOTSTRAP_PROFILE_ADDITIONS` and `tests/test_story_criteria.py` in `_BOOTSTRAP_HUMAN_OWNERSHIP`, so the protected base can own paths this branch adds. - Raise the story-validation strength defaults in `pdd fix`, `pdd test` and the agentic change orchestrator to DEFAULT_STRENGTH (promptdriven#2394), state that contract in user_story_tests_python.prompt, and update the two tests that asserted the stale 0.2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Addressed on 1. Protected-base rollout — fixed, and structured on the #2374 precedentThe root cause was that the retirement and the prompt change were unauthorized together, not that they needed splitting: PR #2374 already solved exactly this shape for the code generator —
The real protected-base-to-candidate load you asked for now passes:
2. Strength 0.2 — every Python caller raised
One site deliberately left alone: 3. Merge conflict — resolved
Validation on
|
Fixes #2389
Problem
run_user_story_testspasses a story only whendetect_changereturns an empty changes list:detect_changeis the change-propagation detector. Its prompt asks the model to "analyze which prompts need to be changed" and to "generate at least three different possible implementation plans" — it is never asked whether anything is wrong. Using an empty answer as the pass criterion makes the verdict depend on model strength rather than on whether the prompts satisfy the contract.Three runs in #2389, same story, same contract, same seven prompts, only
--strengthvaried: a weak model FAILed twice (once on genuine gaps, once on noise — asking to strengthen a rule whose text already said the thing verbatim), a strong model PASSed. Nothing about the prompts changed between runs.There is a second face to the same bug: a malformed or empty detector response also produces an empty changes list, so an evaluator that said nothing at all was scored as success. The gate failed open.
Change
Story validation now asks a bounded question. Each acceptance criterion is classified exactly once.
changes_listunsatisfiedunsatisfiedfails a story.unclearis emitted as acriteria:UNCLEARwarning and never moves the verdict, so a hedging model cannot fail a correct prompt set.detect_change— whose empty answer would read as success.satisfiedverdict must quote prompt text; without a citation it degrades tounclear. Citations that aren't found verbatim are reported (criteria:UNVERIFIED_CITATION) but never fail a criterion, since models paraphrase and a false alarm there would reintroduce the model sensitivity this removes.unclear, neversatisfied.Compatibility
detect_changeis untouched. It still owns change propagation, PRD propagation, andpdd fix.--legacy-detect(CLI) andlegacy_detect=True(library) force every story onto the old gate.run_user_story_testskeeps its(bool, list, float, str)signature, sodrift_main,agentic_change_orchestrator,change_main, andcommands/analysisneed no changes.{prompt_name, change_instructions}rows, andunclearlands in the existing advisory warnings channel —pdd.detect.stories.v1is unchanged, no schema bump.Tests
865 green: 99 story/criteria (30 new unit + 8 new integration), 731 across detect, orchestrator, drift, architecture-sync, evidence and CLI suites, 34
test_sync_core_manifest.py, 81test_sync_core_verification_profiles.py.Known-red CI, and what it needs from a maintainer
7 tests in
tests/test_sync_core_pdd_rollout_policy.pyfail, all from one cause: this PR adds two files underpdd/prompts/, which automatically become managed units, and they are not registered.13933430441ceafd14232eff53e8456628f70ea5a18fd6c233cb40f18547c830a7dd6a8f4145e2a4712ca925abde0edc58c47be271a1a06e1750aa39347a76c7story_criteria_LLM.prompt502768e905f49b587ac821e520002019e758a79aba9fda89c0bf73c7488e3f5dstory_criteria_python.prompte0514f4abcc03f6c41f3ef3cf8978bdb0f4e732d3d8c306f7618469762d8ee4bRemaining steps, all naming the new state:
(base, head)pair beside_SYNC_ROLLOUT_REPAIR_PROFILE_BYTES(verification.py:127) and to the set attests/test_sync_core_pdd_rollout_policy.py:2268.requirement_rotationsrows in.pdd/verification-profile-rotations.jsonwithhead_policy_sha256set to the new digest — their absence is whycurrent_rowsis[]at line 2565.requirement transition bindings mismatchcascade shares this root and clears once 1 and 2 land.test_pdd1875_phase_a_is_dormant_on_its_composed_head(:2192) andtest_replay_transitions_cover_the_actual_protected_base(:2212) assert against a historical head that genuinely holds 469 units; give them their own frozen constant followingPDD_1989_EXPECTED_MANAGED_UNITS(:159), leavingEXPECTED_MANAGED_UNITS = 471for current.The new-profiles digest holds only while
.pdd/verification-profiles.jsonstays byte-identical to that branch; any reformat re-mints it.