Honor architecture.json filepath for prompts in nested .pddrc contexts#1971
Honor architecture.json filepath for prompts in nested .pddrc contexts#1971Serhan-Asad wants to merge 62 commits into
Conversation
|
Companion downstream PR: promptdriven/pdd_cloud#3205 (adds the missing architecture.json entries + a drift gate; closes pdd_cloud#3203). |
|
Addressed the three blockers from the latest review in 1. Direct fast-path symlink escape — containment was only on recursive candidates, so Step 1's direct/case-insensitive lookup returned the exact expected prompt even when it was a symlink to a file outside 2. Broad-root context selection depended on CWD — 3. Two-snapshot architecture race — Nits: fixed both Validation: 212 passed (resolver suite), 465 (path-resolution regressions), 698 (broader sync/agentic/drift); ruff-critical, Follow-up (not in this PR): the acknowledged test-file churn/deletion gate for the auto-heal pipeline remains unimplemented — it belongs in the heal driver, not this path-resolution change, and needs its own change + tests. Tracking it separately. |
|
Addressed the three architecture-lookup blockers from the latest review in 1. Architecture hint ignored context ownership — with a broad project prompts root and 2. Unsafe same-leaf row raised a spurious 3. Also extended the governing Validation (the review noted a local pytest slice segfaulted with 139 in that environment — here the suites run clean): 215 passed (resolver suite), 796 passed (broader sync/agentic/path-resolution); ruff-critical, |
|
Addressed the three blockers from the latest review in 1. Flat legacy filenames bypassed context ownership — territory was applied to the leaf-match and filepath-stem borrows but not the basename+language or flat-filename match loops. A stale sibling row with a flat 2. Canonical lookup stayed CWD-dependent — 3. Unsafe OUTPUT rows still caused false ambiguity — Also extended the governing Validation (noting the earlier report that local pytest crashes before collection in that host — it runs clean here): 220 passed (resolver suite), 801 passed (broader sync/agentic/path-resolution); ruff-critical, |
|
Addressed the three correctness findings and the performance regression from the latest review in 1. Context inference depended on CWD with no override — 2. Custom-root prompt discovery did not receive the anchor — 3. Exact example/test templates stayed CWD-dependent — 4. Performance regression (from the round-5 unsafe-filepath fix) — Also extended the governing prompt and refreshed Validation (local pytest runs clean here despite the earlier pre-collection crash on that host): 224 passed (resolver suite), 819 passed (broader sync/agentic/path-resolution); ruff-critical, |
|
Addressed the three findings from the latest review in 1. New-module outputs resolved outside the subproject — the missing-prompt branch called 2. Non-architecture templates duplicated context prefixes — the existing-prompt template branch (no arch entry) recomputed the basename without the project anchor, and 3. Proven ownership was overridden by territory — good catch. Round 5's territory check was applied unconditionally, so a row whose physical prompt owner IS the resolved prompt was discarded when its authoritative target is intentionally shared (outside the context globs). Extended the governing prompt and refreshed Validation (local pytest runs clean here): 228 passed (resolver suite), 831 passed (broader sync/agentic/path-resolution); ruff-critical, |
|
Addressed the three findings from the latest review in 1. Existing-prompt template outputs stayed parent-CWD-relative — round 7 anchored only the missing-prompt branch. The existing-prompt template branch now anchors project-relative outputs at the subproject too ( 2. Absolute sibling output paths defeated cross-context isolation — 3. Flat architecture hints selected the wrong same-leaf prompt — Extended the governing prompt and refreshed Validation (local pytest runs clean here): 231 passed (resolver suite), 834 passed (broader sync/agentic/path-resolution); ruff-critical, |
gltanaka
left a comment
There was a problem hiding this comment.
The PR is needed: it fixes a real tier-1 resolution failure where nested .pddrc prompt roots strip an architecture filename prefix and make pdd sync fall back to the wrong code template. The normal path is well covered, and I independently verified python -m pdd sync commands/checkup --dry-run resolves prompts/commands/checkup_python.prompt to pdd/commands/checkup.py. The contributor also supplied substantial resolver, broader-suite, differential-sweep, and downstream CLI dry-run evidence.
One correctness blocker remains:
Blocking: context prefix matching is substring-based and can still return a sibling prompt. At pdd/sync_determine_operation.py:345, the new flat-architecture hint tie-break uses context_prefix in str(m.relative_to(prompts_root)). The same predicate remains at lines 1081 and 1115. This does not test path ownership. With contexts prompts/api and prompts/aapi, both containing credits_Python.prompt, a flat architecture row for api/credits.py, and context_override="api", get_pdd_file_paths() returns prompts/aapi/credits_Python.prompt. I reproduced this on head 639002eef; the code target remains in the requested api context, so resolution produces a torn cross-context prompt/code pair. An update can therefore modify the sibling prompt.
Please match exact path components/prefixes rather than substrings (ideally through one shared helper used by all three sites) and add a regression with overlapping context names such as api and aapi. Until that case is covered, the real end-to-end testing is strong but not sufficient to establish the cross-context safety claimed by this PR.
get_pdd_file_paths() resolves a prompt's code deliverable with architecture.json's `filepath` as tier-1, overriding the `.pddrc` code template. The arch.json lookup key was built relative to the `.pddrc` context's `prompts_dir` (e.g. `prompts/backend`), which strips a leading path segment that architecture.json `filename` fields retain (they are stored relative to the repo `prompts/` root, e.g. `backend/credits_Python.prompt`). The key `credits_Python.prompt` never matched the stored `backend/credits_Python.prompt`, so tier-1 silently missed and pdd fell back to the wrong `.pddrc` template (`backend/functions/credits.py`) — for modules whose real code lives elsewhere this yields a non-existent path and "Code file not found". Fix: when the primary architecture.json lookup returns nothing, retry once with the key computed relative to the repo `prompts/` root. Guarded by `alt_lookup != prompt_filename_for_lookup` and `try/except ValueError`, so it is a pure fallback — it only fires on a primary miss and no-ops for repos whose prompt tree is not under `prompts/`, preserving existing behavior (verified: 400+ resolver tests pass, purely additive). Reported downstream: promptdriven/pdd_cloud#3203. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…air prompt Address three review findings on the architecture.json.filepath precedence work: 1. Stale cross-context entries no longer redirect sync. A same-leaf prompt could borrow a sibling-context architecture entry (e.g. `frontend/credits.py`) when the sibling's prompt was deleted but its `architecture.json` row survived — `if not owners: return True` treated the missing owner as eligible and the leaf fallback matched by filename leaf, silently overwriting another context's code. Heuristic leaf/filepath-stem borrows are now restricted to filepaths within the resolving prompt's `.pddrc` context territory (its `paths` globs / output locations) via `_context_owned_filepath`; exact filename matches are unaffected, and non-context projects keep prior behavior (permit when no territory derives). 2. Windows drive-qualified metadata is rejected. `D:/x` / `D:x` are relative to `PurePosixPath` (so they passed the absolute/`..` checks) but escape the repo when joined on Windows. Both `_safe_architecture_prompt_filename` and `_contained_architecture_code_path` now reject any `PureWindowsPath(raw).drive`. 3. Realign `agentic_arch_step13_fix_LLM.prompt` with Issue #617. Its STEP A diagnostics and cleanup checklist still labeled nested prompts and slash-bearing architecture filenames as WRONG / "no slashes", contradicting the same prompt's path-mirroring rules and letting the repair agent flatten the layout this change relies on. Diagnostics, the Strategy-A delete guard, and checklist items 4/6 now state that filenames MUST mirror filepath and nested prompts under a configured `prompts_dir` are correct. Adds regression tests for the stale-sibling borrow and drive-qualified rejection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PDD-Auto-Heal-Checkpoint: success
…ext leak Follow-up review of head ba6df21 (auto-heal bot commit). Address all three merge-blockers plus the governing-prompt durability gap: 1. Prompt discovery could escape through a same-leaf FILE symlink. rglob does not descend into symlinked directories (so the existing directory-symlink test did not cover this), but it DOES yield a file symlink whose is_file() follows the link. _find_prompt_file Steps 3b/3c/4 returned such a candidate; an update then writes through it, overwriting a file outside the repo. Every recursively discovered candidate must now resolve inside prompts_root (_prompt_candidate_within_root), mirroring _resolve_prompt_path_from_architecture. 2. Context isolation failed when resolution ran outside the project CWD. _context_owned_filepath loaded .pddrc from the process CWD and failed open, so a parent/sibling CWD with an absolute prompts root re-opened the stale sibling-context borrow (backend -> frontend/credits.py). It now anchors the .pddrc lookup at the project root (architecture.json's directory). 3. The auto-heal checkpoint (ba6df21) regenerated the resolver test suite, collapsing it from 5,990 lines / 154 functions to 438 / 16 and dropping both the new security tests and longstanding regression coverage. Restore the full suite and refresh .pdd/meta so its prompt/code/test hashes match the on-disk files (drift = 0, computed via the module's own calculate_sha256 / calculate_prompt_hash) — the stale test_hash left by my previous commit is what tripped the destructive heal. NOTE: a durable fix still needs a test-file churn/deletion gate in the heal pipeline so a regeneration can never silently delete a hand-maintained suite; that belongs in the heal driver and is filed as a follow-up. Also update the governing sync_determine_operation prompt to require containment on every recursive candidate, CWD-independent .pddrc anchoring, and Windows drive-qualified rejection, so a future regeneration preserves these invariants. Adds load-bearing regression tests for the file-symlink escape and the parent-CWD context isolation (both verified to fail without the fix). Fixes the trailing- whitespace nit. Full suite: 208 passed; broader sync/agentic suites: 460 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-break, single arch snapshot Third review round on head ee0bf64. Address the three new blockers plus the jq nit: 1. Direct fast path could return an escaping file symlink. Containment was applied only to recursive candidates; the Step 1 direct/case-insensitive lookup returned the exact expected prompt even when it was a symlink to a file OUTSIDE prompts_root, which a later update opens with "w" and truncates. Containment now gates every _find_prompt_file return (Step 1 direct, the architecture-hint join, and the glob fallback). In-root aliases (target inside the resolved root) are preserved. 2. Broad-root context selection depended on the process CWD. _find_prompt_file loaded context_prefix via _find_pddrc_file() (CWD). With a project-level prompts root, two contexts owning a same-leaf prompt, and a parent/sibling CWD, no prefix loaded and the shallowest/lexicographic tie-break returned the WRONG-context (backend) prompt for a context_override="frontend" request. The prefix lookup now anchors at the resolved prompt root. 3. Nested discovery read two architecture snapshots. Prompt discovery parsed architecture.json (via _find_prompt_file Step 3) and get_pdd_file_paths parsed it again for the code path; an atomic rewrite between them pairs a prompt from the old registry with a code target from the new one (torn pair). get_pdd_file_paths now parses ONCE and threads that immutable module snapshot through ambiguity detection, prompt discovery, and code-path selection (also removes the redundant parse). Nit: the architecture-repair prompt's jq diagnostics used `.[]`, which fails for the supported {"modules": [...]} format and misreports "None found"; use `(.modules // .)[]`. Also extends the governing sync_determine_operation prompt with all three invariants, refreshes .pdd/meta so prompt/code/test hashes match on-disk (drift = 0), and adds load-bearing regression tests (each verified to fail without its fix), including an in-root-alias-preserved test and a single-parse assertion. Full suite 212 passed; path-resolution regressions 465; broader sync/agentic/drift 698. FOLLOW-UP (still unimplemented, needs its own change in the heal pipeline): a test-file churn/deletion gate so auto-heal can never silently replace a hand-maintained suite. Tracked separately; not in this path-resolution PR's scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fourth review round on head 8c69760. Three architecture-lookup bugs: 1. Architecture HINT ignored context ownership. With a broad project prompts root and context_override="backend", `_find_prompt_file` Step 3 ran a bare-leaf architecture lookup WITHOUT the resolved context, so it borrowed a sibling `frontend/credits` row and returned the frontend prompt (and frontend/credits.py) via the direct join, before the context-aware recursive fallback. The hint now passes the resolved context so `_context_owned_filepath` rejects a row whose filepath is out of context. 2. Unsafe same-leaf row raised a spurious AmbiguousModuleError. `_architecture_module_choices` counted an unsafe row (`../../foo_Python.prompt`, `/tmp/...`) alongside a valid `foo_Python.prompt`, producing two "distinct" targets and blocking a legitimate module. Unsafe filenames (absolute, parent traversal, backslash, Windows drive) are now excluded from ambiguity counting; a null/empty filename still flows to the filepath-stem branch. 3. `"filename": null` crashed into a cwd-relative default. Case-insensitive and basename/language match loops called `.lower()` on `module.get("filename", "")`, which is `None` for a null value (the default only applies to a MISSING key). The AttributeError was swallowed by the broad get_pdd_file_paths fallback, returning a cwd-relative `foo.py` instead of the architecture `src/foo.py`. All three sites now coerce with `str(module.get("filename") or "")`, so the module resolves by filepath stem. Extends the governing sync prompt with all three invariants, refreshes .pdd/meta (prompt/code/test hashes match on-disk, drift = 0), and adds three load-bearing regression tests (each verified to fail without its fix). Full suite 215 passed; broader sync/agentic/path-resolution 796 passed; ruff-critical, compileall, and git diff --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t, unsafe-filepath ambiguity Fifth review round on head 6d60438. Three deeper variants of the context/metadata issues: 1. Flat legacy filenames bypassed context ownership. Territory checks were on the leaf-match and filepath-stem borrows, but NOT the basename+language or flat-filename match loops. A stale sibling row with a flat `credits_Python.prompt` filename pointing at `frontend/credits.py` was borrowed by a backend resolution (backend sync -> frontend code). `_context_owned_filepath` now gates those loops too. 2. Canonical lookup stayed CWD-dependent. `_get_filepath_from_architecture` re-detected the context from the CWD and stripped the basename prefix via a CWD-based `.pddrc` lookup. From a parent/sibling CWD with an absolute prompts root, a path-qualified `backend/foo` failed to align with the canonical `backend/services/foo.py` row and fell back to the default `backend/functions/foo.py`. It now prefers the caller's resolved context and anchors `_relative_basename_for_context` / `_module_filepath_matches_basename` / `_prompt_basename_candidates` at the architecture project root. 3. Unsafe OUTPUT rows still caused false ambiguity. `_architecture_module_choices` validated the filename but added the raw filepath to the distinct-target set, so a valid `foo -> src/foo.py` row plus a same-filename `foo -> ../../outside/foo.py` (or `/tmp/...`, `D:/...`) read as two targets and raised AmbiguousModuleError. Unsafe filepaths are now validated with the same containment used before generation and excluded from ambiguity counting. Extends the governing prompt with all three invariants, refreshes .pdd/meta (drift = 0), and adds five load-bearing regression tests (each verified to fail without its fix; both halves of #2 — resolved-context preference and project-anchored .pddrc — checked independently). Full suite 220 passed; broader sync/agentic/path-resolution 801 passed; ruff-critical, compileall, git diff --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y perf regression Sixth review round on head ea6528c. Three deeper CWD-dependency variants plus the performance regression round 5 introduced: 1. Context inference still depended on CWD with NO override. `_resolve_context_name_for_basename` searched from the process CWD, so a path-qualified `backend/foo` run from the project's parent (no context_override) missed the backend context and the canonical `backend/services/foo.py`, falling back to `backend/functions/foo.py`. It now accepts a `pddrc_anchor`; get_pdd_file_paths establishes the prompts-root anchor BEFORE resolving context and passes it. 2. Custom-root prompt discovery did not receive the anchor. `_find_prompt_file` built its basename candidates and did path-qualified alignment via a CWD-based `.pddrc` lookup, so from a parent CWD with a custom prompt root the context prefix was not stripped — an existing `specs/services/utils/foo_Python.prompt` was missed and a duplicated `specs/services/backend/utils/foo_Python.prompt` returned. It now anchors both candidate building and the Step-4 alignment at the resolved prompts root. 3. Exact example/test templates stayed CWD-dependent. `construct_paths_basename` was stripped via a CWD-based lookup, so from a parent CWD a `{category}` template duplicated the prefix (`backend/examples/backend/foo_example.py`). It now strips via the project-anchored lookup. 4. PERF (regression from round 5): `_architecture_module_choices` filesystem-resolved EVERY module's filepath for containment before checking the filename match — ~8x slower on a 3,001-module architecture (6,003 resolutions). Containment is now deferred until after the cheap filename/leaf/stem match, so only matching rows are resolved (1 instead of 3,001 in the repro). Extends the governing prompt, refreshes .pdd/meta (drift = 0), and adds four load-bearing regression tests (each verified to fail without its fix; F2 needed BOTH anchor points neutralized, confirming both are load-bearing; PERF asserts <=5 containment resolutions across 301 modules). Full suite 224 passed; broader sync/agentic/path-resolution 819 passed; ruff-critical, compileall, git diff --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n ownership over territory Seventh review round on head 56d757d. Three findings; the round-6 perf regression is confirmed fixed. 1. New-module outputs resolved outside a subproject. The missing-prompt branch called construct_paths with no anchoring input (path_resolution_mode="cwd"), so from a parent CWD a new module's code/test resolved under the parent. It now passes the prompt path as a hint (construct_paths finds the subproject .pddrc and resolves against it), and project-relative template outputs are anchored at the project root (_anchor_output_paths_at_project) — the template branch produced a relative backend/functions/foo.py that was otherwise CWD-resolved. 2. Non-architecture templates duplicated context prefixes. The existing-prompt template branch (no arch entry) recomputed the basename without the project anchor, and _overlay_configured_output_paths had the same omission, so from a parent CWD a {category} template produced backend/examples/backend/foo_example.py. Both now anchor. 3. Proven ownership was overridden by territory. Round 5 added territory to the basename+language / flat / leaf / filepath-stem borrows, but it was applied unconditionally — discarding a row whose physical prompt owner IS the resolved prompt when its authoritative code target is intentionally shared (outside the context globs), falling back and risking a duplicate. `_belongs_to_resolved_prompt` now returns a three-state classification (ineligible / heuristic-eligible / proven); territory guards only heuristic-eligible rows. A PROVEN row keeps a shared/no-context target but is still rejected when the target is owned by a SIBLING context — reconciling round 7 (honor shared) with round 5 (reject a stale frontend row leaf-colliding with a backend prompt). Extends the governing prompt, refreshes .pdd/meta (drift = 0), and adds four load-bearing regression tests (each verified to fail without its fix), including an explicit proven-owner-still-rejects-sibling-context guard so the reconciliation can't silently regress. Full suite 228 passed; broader sync/agentic/path-resolution 831 passed; ruff-critical, compileall, git diff --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… flat-hint context Eighth review round on head a993f8e. Three follow-ups to the round-7 output/ownership work: 1. Existing-prompt template outputs stayed relative to the parent CWD. Round 7 anchored only the missing-prompt template branch; the existing-prompt branch returned project-relative paths, so from a parent CWD an existing prompt's example/test resolved under the parent. It now anchors too (via _anchor_output_paths_at_project), and that helper re-anchors ONLY when the project root differs from the CWD — so the established relative-output contract (CWD == project) is preserved. 2. Absolute sibling output paths defeated cross-context isolation. _filepath_matches_context compared raw config prefixes against the project-relative architecture filepath, so an absolute generate_output_path never matched and a sibling context stopped owning its code — a stale flat backend row targeting frontend/ was then accepted. Absolute .pddrc paths/output values are now re-expressed relative to the project (an absolute value outside the project can never own a project-relative target). 3. Flat architecture hints selected the wrong same-leaf prompt. _resolve_prompt_path_from_architecture sorted duplicate recursive matches by shallowest/lexicographic without context, so a legacy flat filename matching the leaf in both backend/ and frontend/ returned the backend prompt for a frontend request while code resolved under frontend (a torn pair). It now prefers the resolving context's prefix (threaded from _find_prompt_file). Extends the governing prompt, refreshes .pdd/meta (drift = 0), and adds three load-bearing regression tests (each verified to fail without its fix). Full suite 231 passed; broader sync/agentic/path-resolution 834 passed; ruff-critical, compileall, git diff --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
639002e to
f5bce25
Compare
…t hint; R7/R10 coverage Round-7 codex review (gpt-5.6-sol). Four findings addressed. CODE (MAJOR) — ambiguity counting (_architecture_module_choices) compared filepath stems case-sensitively while resolution is case-insensitive, so two case-variant outputs undercounted and an ambiguous bare module fell through instead of raising. Now case-insensitive (matches the round-6 resolution fix). Regression: test_get_pdd_file_paths_case_variant_stem_outputs_raise_ambiguous. CODE (MAJOR) — the architecture-hint prompt lookup applied the context_prefix filter only to its recursive matches; the direct join and the final lexical fallback returned without it, so a flat/stale hint could pair a wrong-context prompt with the requested context's code. Every hint return path now enforces _prompt_path_has_context_prefix when a context prefix is active (covered by the existing context-isolation suite). PROMPT (MAJOR) — the resolved-symlink containment of architecture OUTPUT paths had no contract rule. R7 now states it explicitly (a code filepath that resolves outside the project after following symlinks is discarded) and maps test_get_pdd_file_paths_rejects_symlink_architecture_escape. PROMPT (MINOR) — mapped R10 to test_get_pdd_file_paths_rejects_degenerate_basename (dot/trailing/duplicate-separator cases) alongside the whitespace/control test. Refreshed both fingerprints + run report (313 passing). No drift. Resolver 313 passed / 2 skipped; architecture_sync 147; construct_paths+backward_compat+agentic_architecture 172. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…coverage Round-8 codex review (gpt-5.6-sol). Three findings addressed. CODE (MAJOR) — _project_relative left relative context globs un-normalized, so a "./frontend/**" paths glob did not match the normalized project-relative architecture filepath and sibling territory was silently missed (the prefixes branch already stripped "./"; the globs branch did not). Relative values are now normalized via PurePosixPath. Regression: test_filepath_matches_context_normalizes_dot_slash_glob. (The reported Windows case-insensitive-drive comparison is a separate, non-reproducible edge on POSIX; not changed.) PROMPT (MINOR) — R2 had been narrowed until it merely restated R5/R6's sibling-target prohibition while its coverage cited prompt-selection tests. R2 now states its distinct obligation — the architecture hint must select a prompt aligned with the resolving context and basename so prompt and code resolve under the same context — leaving code-target ownership to R5/R6. PROMPT/TEST (MINOR) — R13's only test exercised null; added a parametrized test_get_pdd_file_paths_non_string_filename_uses_filepath covering number/bool/list/ object filenames (all resolve by filepath stem) and mapped it. Refreshed both fingerprints + run report (319 passing). No drift. Resolver 319 passed / 2 skipped; architecture_sync 147; construct_paths+backward_compat+agentic_architecture 172. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round-9 codex review (gpt-5.6-sol). Two findings addressed; one assessed non-actionable. CODE (MAJOR) — PROVEN ownership did not require a UNIQUE physical prompt owner, so a flat/same-leaf architecture filename matching distinct prompts in two context roots was classified as proven for each, letting both contexts claim one shared code target. It now stays territory-guarded (ELIGIBLE) unless exactly one physical owner matches. Load-bearing regression: test_get_pdd_file_paths_flat_same_leaf_in_two_roots_not_proven (fails without the guard). Full suite unaffected. PROMPT (MINOR) — R14 pinned the existence/level/timing/representation of an internal INFO log record. Reworded to the observable log-safety behavior only: no raw, unvalidated caller/architecture path value in any log; validated values are logged only in escaped, normalized form. NOT CHANGED — the reported "generate_output_path relocation is checked before relocation" finding targets the resolving context's own trusted .pddrc output config; relocating a bare filepath into a context's configured output dir is intended, and re-running sibling-territory checks on a context's own config would reject valid configurations. The untrusted-metadata threat model (architecture.json) is already validated. Refreshed both fingerprints + run report (320 passing). No drift. Resolver 320 passed / 2 skipped; architecture_sync 147; construct_paths+backward_compat+agentic_architecture 172. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round-10 codex review (gpt-5.6-sol) — single MINOR finding. Requirement #6 still named private `_find_prompt_file`/`construct_paths` and duplicated the Instruction #3 pointer. Reworded to a purely behavioral statement: the resolution obligations are the stable contract rules R1-R15 (enforced by <coverage>), with implementation strategy unconstrained. No code/behavior change; sync_determine_operation prompt_hash refreshed; no drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…owner + R14 contracts Round-11 codex review (gpt-5.6-sol). Three findings addressed. CODE (MAJOR) — territory matching compared drive-qualified Windows paths (and their components) case-sensitively, so a drive/directory casing difference between .pddrc and the resolved project root hid sibling ownership on Windows (a reproduced probe returned False for a physically matching sibling root). _filepath_matches_context now folds case throughout when the project root is a Windows path; POSIX roots stay case-sensitive (unchanged). Regression: test_filepath_matches_context_windows_paths_are_case_insensitive. PROMPT (MAJOR) — the <vocabulary> "proven-owner row" definition now states the unique- physical-owner condition the round-9 code enforces (a flat/same-leaf name matching prompts in more than one context root is heuristic, not proven), and R6 coverage now maps the round-9 regression test_get_pdd_file_paths_flat_same_leaf_in_two_roots_not_proven. PROMPT (MINOR) — R14 promised "normalized" logging of accepted values, but accepted paths are logged escaped (via %r), not normalized. R14 now states the actually- implemented guarantee: no raw unvalidated value in any log; validated values rendered safely (escaped) so control/newline/ANSI content cannot forge or split a log line. Refreshed both fingerprints + run report (321 passing). No drift. Resolver 321 passed / 2 skipped; architecture_sync 147; construct_paths+backward_compat+agentic_architecture 172. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rpen R8/Instruction-3 Round-12 codex review (gpt-5.6-sol). Three findings addressed. CODE (MAJOR) — when construct_paths raises while resolving a NEW module, the convention fallback built code/example/test paths relative to the process CWD, so a new module resolved from a parent/sibling CWD landed its artifacts under the wrong root. The fallback now anchors at the resolved subproject (.pddrc dir) when it differs from CWD, staying relative when they coincide (legacy contract preserved). Regression: test_get_pdd_file_paths_construct_paths_failure_anchors_fallback_at_subproject. PROMPT (MAJOR) — R8 said "fail closed" for every escape, but its cited tests split: an escaping DISCOVERY candidate is discarded and resolution continues with fallback (rejects_symlink_prompt_discovery_escape), while the resolving context's OWN expected prompt escaping hard-fails (nested_escaping_symlink_is_hard_failure). R8 now states both behaviors distinctly, matching all its coverage tests. PROMPT (MINOR) — Instruction #3 still pinned `construct_paths`, contradicting Requirement 6's "implementation strategy unconstrained". Reworded to the observable return shape + template/legacy-fallback behavior, no internal-call pin. Refreshed both fingerprints + run report (322 passing). No drift. Resolver 322 passed / 2 skipped; architecture_sync 147; construct_paths+backward_compat+agentic_architecture 172. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nonicality Round-13 codex review (gpt-5.6-sol) — single MINOR finding. The <vocabulary> "valid output" definition cited R7-R10, but R8 governs prompt paths and R10 governed only caller basenames, so architecture output canonicality was ambiguous and uncovered. - "valid output" now names the checks that actually apply to output filepaths: R7 (relative/contained/no-traversal/backslash/drive/symlink-escape), R9 (portable components), R10 (canonical spelling); R8 is noted as prompt-only. - R10 now explicitly covers architecture output filepaths (the _contained_architecture_code_path `as_posix() != raw` canonicality check), and R11's ambiguity reference is R7/R9/R10. - Added test_contained_architecture_code_path_rejects_noncanonical (dot segments, duplicate/trailing separators, no-components) and mapped it to R10. No code change; sync_determine_operation prompt_hash + run report (328 passing) refreshed; no drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ging) Round-14 codex review (gpt-5.6-sol) — two MINOR coverage-mapping findings. - R6 coverage now includes test_get_pdd_file_paths_proven_owner_honored_for_shared_target, the positive case verifying a proven owner keeps an unowned/shared target (its coverage previously listed only boundary/rejection cases). - R14 coverage now maps the existing basename log-injection test (test_get_pdd_file_paths_validates_before_logging_raw_input) and a new test_get_pdd_file_paths_validates_language_before_logging (control/newline/ANSI-bearing language never reaches an INFO record before rejection), so all four R14 input sources have a negative logging test. No code change; sync_determine_operation prompt_hash + run report (332 passing) refreshed; no drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… for R12 Round-15 codex review (gpt-5.6-sol) — two MINOR findings. - R10 now also covers non-canonical architecture prompt FILENAMES (HEAD's _safe_architecture_prompt_filename already rejects ./foo_Python.prompt, a//..., trailing slash, etc.); added test_safe_architecture_prompt_filename_rejects_noncanonical and mapped it to R10. - R12's coverage now cites only the behavioral no-torn-pair test, not the parse-count mechanism test (which enforced a specific implementation). test_get_pdd_file_paths_parses_architecture_once remains in the suite as an implementation regression (per the earlier human review) but is no longer the rule's evidence. No code change; sync_determine_operation prompt_hash + run report refreshed; no drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ot siblings
Round-16 codex review (gpt-5.6-sol). One MAJOR, one MINOR.
CODE (MAJOR) — the proven-owner check projected the resolved prompt's relative path
across EVERY prompt root, so a uniquely-named SIBLING prompt sitting at the same relative
path under a different context root could be misclassified as the resolved prompt's
proven owner and lend its shared code target across contexts. The projection is now
restricted to roots that ALIAS the resolving prompt root (resolve to the same directory);
sibling roots are excluded. Resolving the root directories (not the caller-influenced
prompt path) preserves the prior no-filesystem-sink hardening. Full proven-owner /
sibling / alias suite unchanged (484 passed).
PROMPT (MINOR) — R11 pinned the internal ordering ("before any prompt or fallback
resolution"), which is not observable. Reworded to the observable outcome: raise
AmbiguousModuleError rather than returning any resolved path or generating.
Refreshed both fingerprints + run report. No drift. Resolver 484-suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…alified ambiguity Round-17 codex review (gpt-5.6-sol). Three findings addressed. CODE (MAJOR) — a DISCOVERED architecture.json that is present but unreadable/malformed was silently downgraded to an empty registry, so resolution proceeded at convention fallback paths instead of the authoritative registered target. It now fails closed with a new MalformedArchitectureError (an AmbiguousModuleError subclass, so every sync entry point that already propagates that error fails fast); a genuine delete race (FileNotFoundError after discovery) still degrades to no-registry. Regression: test_get_pdd_file_paths_malformed_architecture_json_fails_closed. CODE (MAJOR) — ambiguity detection returned [] for path-qualified basenames, but a qualified basename matches by path-SUFFIX so more than one distinct valid output can align (app/login/page -> app/login/page.py AND src/app/login/page.py) and was resolved by architecture row order. _architecture_module_choices now collects distinct valid suffix-aligned outputs for qualified basenames so the caller raises AmbiguousModuleError; a single/zero match keeps canonical resolution unchanged (all issue_1677 tests pass). Regression: test_get_pdd_file_paths_path_qualified_two_suffix_aligned_outputs_raise_ambiguous. PROMPT (MINOR) — R7 said unsafe metadata causes configured-output fallback; it now says the offending row is discarded, resolution continues with the other valid architecture rows, and configured outputs are used only when no valid row remains (consistent with R11). Refreshed both fingerprints + run report. No drift. 695 passed across resolver + arch + issue_1677 + construct_paths + backward_compat + agentic_architecture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… filenames from qualified ambiguity Round-18 codex review (gpt-5.6-sol). Three findings addressed (follow-ons to round 17). CODE (MAJOR) — the round-17 fail-closed handled unparseable JSON but valid JSON with an UNSUPPORTED schema (a top-level scalar, or a dict whose ``modules`` is not a list) still flowed through extract_modules as an empty registry -> convention fallback. Schema is now validated: a bare list or a dict are accepted (a dict MAY omit ``modules`` = legitimately empty), but a scalar or a non-list ``modules`` raises MalformedArchitectureError. Regressions: parametrized malformed test (scalar / non-list modules) + test_get_pdd_file_paths_empty_registry_dict_is_not_malformed (empty dict is NOT malformed). CODE (MAJOR) — the round-17 path-qualified ambiguity count did not exclude rows with an unsafe architecture FILENAME (resolution already treats them ineligible), so an invalid row could inflate the count and falsely block a valid mapping. The qualified branch now skips unsafe-filename rows (matching the bare branch). Regression: test_get_pdd_file_paths_path_qualified_unsafe_filename_row_does_not_block. PROMPT (MAJOR) — R11 said "bare basename" only while its coverage cited a path-qualified test. R11 now covers both: a bare basename (two rows -> distinct outputs) and a path-qualified basename (two rows' outputs both path-suffix-align), with unsafe rows (invalid filename OR output) excluded. Refreshed both fingerprints + run report. No drift. 528 passed across resolver + arch + issue_1677. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ry before containment
Round-19 codex review (gpt-5.6-sol). Two addressed; one declined with rationale.
CODE (MAJOR) — the round-18 schema validation accepted a modules list containing
non-object entries, which extract_modules silently discards, letting a corrupted registry
fall through to convention paths. A modules list with any non-object entry now raises
MalformedArchitectureError. Regression: parametrized malformed test gains the
`{"modules": [{...}, 42]}` case.
CODE (MINOR, perf) — the recursive prompt fallback resolved symlink containment for EVERY
*.prompt in the tree before checking the filename leaf. It now filters by the cheap
filename leaf first and pays the containment resolve only for the handful of leaf-matching
candidates (expected leaves computed once). Behavior-preserving (all nested/symlink tests
green); drops filesystem I/O proportional to the whole prompt tree.
NOT CHANGED — the "malformed .pddrc -> permissive territory" finding: a malformed .pddrc
yields no valid territory configuration, so resolution degrades to no-context-isolation
exactly as an absent .pddrc does (the "sibling" context it references is itself defined by
the file that will not parse). This graceful degradation is applied consistently at ~10
_load_pddrc_config call sites; failing closed at only the territory site would be
inconsistent, and repo-wide fail-closed on any .pddrc syntax error is out of scope.
Refreshed both fingerprints + run report. No drift. 529 passed across resolver + arch +
issue_1677.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…put vocab Round-20 codex review (gpt-5.6-sol). Two findings addressed. CODE (MAJOR) — path-qualified ambiguity detection matched outputs against the RAW basename while final resolution matches against the context-relative basename (context prefix stripped). A context-prefixed qualified basename could therefore have two suffix-aligned outputs evade AmbiguousModuleError and resolve by row order. _architecture_module_choices now takes the resolved context and uses the same context-relative basename + anchor as resolution (only the fail-fast call passes it; the example/test-stem call is unchanged). Regression: test_get_pdd_file_paths_context_prefixed_qualified_ambiguity_uses_stripped_basename. PROMPT (MINOR) — the "valid output" vocabulary attributed backslash / Windows-drive rejection to R7, contradicting R7's own text (those belong to R9). The vocabulary now scopes R7 to relative/contained/traversal/symlink-escape and points backslash/drive/ device/control rejection to R9. Refreshed both fingerprints + run report. No drift. 530 passed across resolver + arch + issue_1677. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… unparseable .pddrc; align unsafe hard-fail Round-21 codex review (gpt-5.6-sol). Four findings addressed. CODE (MAJOR) — ambiguity detection stripped the architecture filepath BEFORE containment validation, so a trailing-space / noncanonical row (which resolution rejects) was canonicalized and counted, falsely conflicting with a valid row. Both ambiguity branches now validate the RAW filepath. Regression: test_get_pdd_file_paths_trailing_space_output_does_not_block_valid_row. CODE (MAJOR) — a present-but-unparseable .pddrc was swallowed into territory_config=None, and _context_owned_filepath treats "no config" as permissive, so a heuristic borrow targeting a sibling context was permitted (fail open). Territory is now marked _TERRITORY_MALFORMED (distinct from absent), and a heuristic (non-proven) borrow is denied when territory cannot be parsed; a proven explicit mapping is still honored. R5 documents this. Regression: test_get_pdd_file_paths_malformed_pddrc_denies_heuristic_borrow. CODE (MINOR) — the recursive prompt fallback hard-failed on an escaping same-leaf symlink even under an UNRELATED directory that does not align with a path-qualified basename. The unsafe hard-fail set is now filtered by the same basename-directory alignment as the safe matches. Regression: test_get_pdd_file_paths_unaligned_escaping_symlink_does_not_block_qualified_creation. PROMPT (MINOR) — R14 pinned "escaped" as the logging mechanism; reworded to the observable requirement (no logged value may let control/newline/ANSI content forge or split a record, however achieved). Refreshed both fingerprints + run report. No drift. 533 passed across resolver + arch + issue_1677. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…established Round-22 codex review (gpt-5.6-sol). Two findings addressed. CODE (MAJOR) — when context_override is omitted and the basename does not encode a context, resolved_context_name is None; _filepath_owned_by_other_context returns False for a None context and _context_owned_filepath is permissive, so a FOREIGN heuristic row (matched by filepath stem, not naming this module) targeting a named sibling context was borrowed — pairing the prompt with sibling-context code (repro confirmed). Now, when the resolving context cannot be established, a heuristic borrow whose target lies in ANY named context is denied (fail closed) via new _filepath_in_named_context — UNLESS the row names this module (its filename leaf equals the requested prompt filename, e.g. a new module with no physical prompt yet), which is still honored, as are proven mappings and unowned/shared targets. Regression: test_get_pdd_file_paths_no_override_none_context_denies_foreign_sibling_borrow. (An initial broad fix that derived the context from the prompt root was reverted — it returned the catch-all `default` and broke a cross-directory proven layout.) PROMPT (MINOR) — Instruction 3 duplicated Requirement 6's R1-R15 statement; it now carries only the public interface + return shape and points to Requirement 6 for the contract. Refreshed both fingerprints + run report. No drift. 497 passed across resolver + arch; full resolver suite 350. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fail to active root Round-23 codex review (gpt-5.6-sol). Three findings addressed. CODE (MAJOR) — an exact architecture row that NAMES the requested module but whose prompt is not created yet (new module), with a safe shared/unowned target, was rejected because eligibility required context ownership or a physical proven owner — so sync fell back to a convention path. _borrow_ownership_ok now honors a row whose filename exactly matches the requested prompt filename (its own explicit mapping); sibling-owned and unsafe targets were already rejected earlier, so this cannot cross contexts. Regression: test_get_pdd_file_paths_exact_missing_prompt_row_shared_target_honored. CODE (MAJOR) — an escaping same-leaf symlink in an UNRELATED auxiliary prompt root made _architecture_prompt_owner's all_contained False and invalidated a unique contained owner in the ACTIVE root, silently replacing its authoritative mapping with fallback paths. The containment verdict is now scoped to the ACTIVE root (auxiliary-root escapes are ignored when the active root supplies the owner; the active context's own expected prompt escaping still hard-fails). Regression: test_get_pdd_file_paths_auxiliary_root_symlink_escape_does_not_invalidate_active_owner. PROMPT (MINOR) — defined "row that names this module" and "unowned/shared target" in <vocabulary> (observable filename-match / context-territory criteria) so R5's exceptions are unambiguous. Refreshed both fingerprints + run report. No drift. 499 passed across resolver + arch; full resolver suite 352. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…st qualified ambiguity Round-24 codex review (gpt-5.6-sol). Two addressed; one declined with rationale. CODE (MAJOR) — an absolute custom prompt root OUTSIDE any project made architecture.json and .pddrc discovery start from that external root, silently missing the caller's project's authoritative mapping. Config/architecture discovery now falls back to the project (CWD) when the prompt root finds no config up-tree; a nested subproject root (which does find its own config) still anchors at itself. Regression: test_get_pdd_file_paths_external_absolute_prompt_root_finds_project_architecture. CODE (MINOR, perf) — the path-qualified ambiguity branch resolved containment on disk for every valid-looking row before the basename-suffix/extension checks. It now does the cheap pure-string checks first and pays the filesystem containment resolve only for matching rows (raw-filepath validation preserved). Behavior-preserving; issue_1677 ambiguity tests green. NOT CHANGED — the "context-qualified filename not recognized as this module" finding: the proposed leaf comparison over-matches a stale SIBLING row (frontend/credits_Python.prompt for a backend request) and re-opens the cross-context borrow (breaks does_not_borrow_stale_sibling_context_entry / context_isolation_holds_from_parent_cwd). Correctly distinguishing an own context-qualified row from a sibling requires establishing the resolving context, which is None in the reported scenario — the exact-match comparison is retained as the safe choice. Refreshed both fingerprints + run report. No drift. 537 passed across resolver + arch + issue_1677; full resolver suite 353. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…anchor in arch branch; trim vocab Round-25 codex review (gpt-5.6-sol). Three findings addressed. CODE (MAJOR) — path-qualified ambiguity counted every suffix-aligned filepath even when the row's PROMPT filename named a DIFFERENT module, raising a false AmbiguousModuleError before selection could choose the uniquely named row. It now mirrors selection eligibility: a row counts only if its filename names this module OR its filename is null/non-prompt (filepath-stem eligible); a prompt filename naming a different module is skipped. Regression: test_get_pdd_file_paths_qualified_foreign_named_suffix_row_no_false_ambiguity. CODE (MAJOR) — the round-24 config_anchor fix missed the architecture branch's and other .pddrc lookups (example/test destinations, new/existing-prompt template branches), so an external absolute prompt root still resolved those from the external root, silently replacing project-configured destinations with defaults. All config .pddrc discovery now threads config_anchor (the anchor-selection check itself stays on the prompt root). PROMPT (MINOR) — the <vocabulary> "row that names this module" / "unowned-shared target" entries embedded ownership BEHAVIOR (honoring prompt-less rows, allowing shared targets) that R5/R6 also state. Vocabulary is now identity-only; R6 carries the "a row that names this module (even with no physical prompt yet) keeps an unowned/shared target unless sibling-owned" permission. Refreshed both fingerprints + run report. No drift. 537 passed across resolver + arch + issue_1677; full resolver suite 354. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on); end-to-end R10 coverage Round-26 codex review (gpt-5.6-sol). Two findings addressed. CODE (MAJOR) — a whitespace-only architecture filename bypassed the ambiguity unsafe-filter (the guard required filename.strip() to be truthy) and was counted, even though selection treats it as INELIGIBLE (_safe_architecture_prompt_filename fails) — a false AmbiguousModuleError could block a valid uniquely-named module. Both ambiguity branches now skip a non-empty string filename that fails validation (whitespace included); an EMPTY string and null/non-string stay filepath-stem-eligible, matching selection and R13. Regression: test_get_pdd_file_paths_whitespace_filename_row_does_not_inflate_ambiguity. PROMPT/TEST (MINOR) — R10's coverage cited only private-helper tests for architecture filename and output-path canonicality. Added an end-to-end get_pdd_file_paths test asserting a noncanonical output (dot segment) is rejected and resolution falls back to a canonical path, and mapped it to R10. Refreshed both fingerprints + run report. No drift. 538 passed across resolver + arch + issue_1677; full resolver suite 356. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Coordination note from the #1998 stack orchestrator ( Overlap detected: both this PR and #1998 modify
These are distinct, non-duplicative concerns, but they touch the same function and will conflict at merge to Flagging so the manager sequences the two and the second-lander resolves the |
Independent review found R7-R10 containment was applied only to architecture.json code filepaths, not to .pddrc-derived output destinations. A .pddrc generate_output_path / example_output_path / test_output_path / outputs.<artifact>.path with parent traversal (or an away-pointing absolute path) let get_pdd_file_paths return code/example/ test targets outside the project, and the non-architecture fallback even mkdir'd a probe directory out of tree during --dry-run resolution. Add UnsafeOutputPathError + a multi-root project-boundary containment (_resolution_containment_roots / _output_path_within_project / _ensure_output_within_project). Every get_pdd_file_paths return is routed through _finalize_output_paths, and the temp-probe mkdir/touch is skipped when the code target escapes, so a configured output that resolves outside every legitimate boundary fails closed instead of writing out of tree. The boundary is the set of legit anchors (config/prompt-root anchor, CWD, and the .pddrc/architecture project root) so parent/sibling-CWD layouts that anchor outputs at CWD are still honored. Prompt: add observable contract rule R16 + "project boundary" vocabulary + R16 coverage. Refresh sync_determine_operation .pdd/meta fingerprints (drift = 0). Regression: 4 escape scenarios (arch example/test, arch generate, arch outputs template, non-arch generate + no out-of-tree mkdir), an in-project positive control, and two real `pdd sync --dry-run` Click CliRunner boundary tests (nested-context resolution + escaping-.pddrc negative control) covering the get_pdd_file_paths-only test gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PDD-Auto-Heal-Checkpoint: success
…y (R16) Independent round-2 review (gpt-5.6-sol xhigh) found three residual trust-boundary gaps on top of the R16 containment landed in dfd737d: 1. BLOCKER — SyncLock built its lock filename from raw basename/language and is acquired BEFORE get_pdd_file_paths validates them, so a traversal-bearing language (e.g. "/../../tmp/victim") could mkdir/touch/unlink a .lock file out of tree. Lock names are now built from a sanitized, separator-free token (_safe_lock_component) and asserted under the locks dir. 2. BLOCKER/HIGH — .pddrc output destinations (generate/example/test_output_path and outputs.<artifact>.path templates) received containment only, so a parent-traversal ("../victim/", CWD-dependent) or a non-portable destination (reserved device, NTFS ADS colon, drive marker, control char) was accepted. They now get the same portable/canonical validation as architecture code filepaths, applied to the RAW configured value up front (_reject_unsafe_pddrc_output_config, before any branch-local `except ValueError` can swallow the error and before Path.resolve() normalizes away a `..`) — making the trust boundary CWD-independent. Multi-root containment remains as defense in depth. 3. R16 prompt pointers now read R1-R16, and the negative CLI control asserts the run is not-ok with an out-of-tree path-resolution failure (not just absence of a stray file). Also regenerate both units' .pdd/meta fingerprints and run reports so the committed provenance matches the tree exactly (metadata_sync include-dep hash, prompt hashes, run-report test hashes); `pdd sync <unit> --dry-run` reports zero drift for both. Adds lock-confinement, non-portable/traversal .pddrc, and mutable entrypoint regression tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… prompt (R16/R8/R17) Independent round-3 review (gpt-5.6-sol xhigh) found the round-2 R16 fix still had reachable trust-boundary gaps; all addressed: 1. BLOCKER — `.pddrc outputs.prompt.path` could override the discovered prompt with a foreign/absolute path; `_finalize_output_paths` only validated code/example/test. It now also holds the returned prompt to the prompts root (R8), resolving both sides so an in-root symlink alias is preserved. 2. BLOCKER — the multi-root containment admitted the process CWD, so a benign relative `.pddrc` output (e.g. `generate_output_path: "sibling-output/"`, no `..`) resolved from a PARENT cwd escaped the governing project. Output authority now comes solely from PROVENANCE: `_governing_output_root` returns the single governing `.pddrc`/`architecture.json` directory, CWD-anchored outputs are RE-ANCHORED under it (`_reanchor_output_to_root`) before the containment check, and the probe mkdir is skipped for anything outside it. 3. HIGH — a nearer descendant `.pddrc` selected by construct_paths bypassed the early raw gate, and a missing-prompt broad `except Exception` swallowed the containment error. `_ensure_output_within_root` now also rejects non-portable components on the RESOLVED path (catches a nearer-config `CON/` that survives resolve() on POSIX), and `except AmbiguousModuleError: raise` guards precede the missing-prompt and construct_paths broad fallbacks so an out-of-tree target fails closed instead of degrading to an unvalidated path. Prompt doctrine: add observable R17 (lock confinement) and map the lock tests to it (was mislabeled R2); state R2 as the observable obligation, not the sanitizer regex; update pointers to R1-R17; redefine the "project boundary" vocabulary as the single provenance-based root (no CWD authority). Regenerate both units' fingerprints + run reports (drift 0). Adds outputs.prompt.path, parent-CWD re-anchor, nearer-.pddrc, and fail-closed missing-prompt regression tests. Rejected round-3 low finding (remove the architecture parse-count test): the manager explicitly asked to keep it as an implementation regression for R12. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…utputs (R16) Round-4 independent review (gpt-5.6-sol xhigh) follow-ups: - HIGH: an explicit absolute `.pddrc` output pointing outside the governing project was silently re-anchored INTO a wrong in-project location by the round-3 re-anchor. Absolute configured values that resolve outside the project root now fail closed at the raw gate (`_configured_output_escapes_root`); only relative values (indistinguishable from a legitimate CWD expansion once resolved) are re-anchored. - HIGH: control/format characters in a resolved output component are now rejected by the resolved-path check too (`_contains_disallowed_path_text`), covering a value that reaches a sink via a nearer descendant `.pddrc` construct_paths selected. - Doctrine: R16 restated as the observable rejection outcome (no "RAW before resolve" sequencing); R1 now defines the supported architecture.json shapes so "malformed" is unambiguous. Adds absolute-escape and control-component regression tests; fingerprints regenerated (drift 0 both units). Rejected (documented): the "external prompt-root appoints its own project authority" finding is the intended nested-subproject behavior (Requirement 6 / R3 / R7, covered by test_get_pdd_file_paths_external_absolute_prompt_root_finds_ project_architecture) — a caller pointing prompts_dir at a tree with its own config anchors there by design, and it is not an out-of-tree escape. The metadata-deletion wording the reviewer flagged in Requirement 1 vs 3.7 is pre-existing on main and belongs to the conflict/metadata subsystem, not this PR's path-resolution scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…back (R16) Round-5 independent review (gpt-5.6-sol xhigh) found one blocker (its other two candidate findings — substring context selection and torn arch snapshot — were confirmed non-issues): - BLOCKER: a truthy NON-STRING `.pddrc` output value (e.g. `generate_output_path: 123`) slipped past `_configured_output_string_is_unsafe` (which treated every non-string as "absent"), then raised `AttributeError` in `str`-only path handling; the outermost `except Exception` returned a basename convention path resolved against the (parent) CWD WITHOUT the finalizer — an out-of-project write target. Now a present non-string value is malformed and fails closed, and the outer last-resort fallback is routed through `_finalize_output_paths` too (re-anchored under the governing root + contained), so no exception-driven fallback can return an uncontained path. Adds non-string generate_output_path (parent-CWD) and non-string outputs template regression tests; fingerprints regenerated (drift 0 both units). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ompts_dir (R16) Round-6 independent review (gpt-5.6-sol xhigh) follow-ups: - P1: discovered `test_files` (globbed siblings handed to the test runner) were re-anchored but not contained, so a `test_<name>*.py` symlink pointing outside the project could be executed. `_finalize_output_paths` now drops any test_files entry that resolves outside the governing root (keeping the validated primary test path), so an out-of-project file is never returned. - P1: a nearer descendant `.pddrc` (governing the resolved prompt's own subtree) was not raw-validated — its `generate_output_path: "safe/../src/"` normalized the `..` away instead of failing closed. The finalizer now re-validates the `.pddrc` nearest the resolved prompt (R16 rejects `..` even when it normalizes back in-project). - P2: a present non-string `.pddrc` `prompts_dir` degraded (via AttributeError in the string-only prefix extractor) to a wrong convention path; it now fails closed in the up-front gate. - Made the mutable-entrypoint lock test load-bearing (captures the constructed SyncLock path and asserts confinement, independent of cleanup timing). Adds test_files-symlink, nearer-.pddrc-traversal, and non-string-prompts_dir regression tests; fingerprints regenerated (drift 0 both units). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Problem
get_pdd_file_paths()givesarchitecture.json.filepathtier-1 precedence for code outputs, but a nested.pddrcprompts_dircan strip filename prefixes retained by architecture entries. The original fallback fixed only repositories rooted at<project>/prompts, still missed canonical/custom prompt layouts, ignored exact example/test output templates, reparsed architecture on misses, and made uncontained architecture paths reachable by sync.Downstream report: promptdriven/pdd_cloud#3203. Tracking issue: #1976.
Fix
prompts.prompts -> pdd/prompts..pddrcoutputs.example.pathandoutputs.test.pathtemplates.Undeclared deliverables still require a downstream architecture entry; core intentionally does not guess a non-template output path when no source-of-truth entry exists.
Validation
compileall, Ruff critical selectors, andgit diff --checkpassed.0.0.301.dev0; local drift detection reports no prompt or example drift for the affected modules.Closes #1976.