Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
00bc81e
feat: add verifiable global sync foundation
Jul 10, 2026
0b61185
feat: enforce canonical sync proof boundaries
Jul 10, 2026
ece9252
fix: harden sync transaction destination writes
Jul 10, 2026
8f876a2
fix: block unstaged protected sync mutations
Jul 11, 2026
3abece7
feat: bind candidate wheel to sync certificate
Jul 11, 2026
a6e2eaf
docs: record round nine sync blockers
Jul 11, 2026
1d7e329
test(sync): reproduce foundation CI regressions
Jul 11, 2026
b7027e7
fix(sync): resolve foundation CI regressions
Jul 11, 2026
3ca27ed
test(sync): reproduce remaining CodeQL alerts
Jul 11, 2026
9920af4
fix(sync): eliminate remaining CodeQL alerts
Jul 11, 2026
9ee95f6
test(sync): reproduce remaining foundation regressions
Jul 11, 2026
421ec3a
fix(sync): restore foundation compatibility boundaries
Jul 11, 2026
068a7ec
test(sync): reproduce xhigh review findings
Jul 11, 2026
b9245a9
fix(sync): harden xhigh certification boundaries
Jul 11, 2026
b6cedd5
test(sync): reproduce xhigh re-review findings
Jul 11, 2026
5bdd7a3
fix(sync): close xhigh re-review gaps
Jul 11, 2026
0be869c
fix: harden sync certification harness
Jul 11, 2026
55a4ab8
test(sync): reproduce top-level pytest probe shadow
Jul 11, 2026
a656364
fix(sync): load pytest collection probe by absolute path
Jul 11, 2026
e4f5eeb
test(heal): reproduce manual-merge operation failure
Jul 11, 2026
bab1c56
fix(heal): skip manual-merge terminal operations
Jul 11, 2026
c0bb303
fix: harden candidate lifecycle wheel install
Jul 11, 2026
6bf14c6
test(sync): cover pytest support isolation gaps
Jul 11, 2026
f586551
fix(sync): bind pytest support and private home
Jul 11, 2026
24f21d8
test(sync): reproduce unbound candidate wheel provenance
Jul 11, 2026
6e8f955
fix(sync): bind candidate wheel to certified source
Jul 11, 2026
4a30a97
fix(sync): verify candidate provenance in certificate paths
Jul 11, 2026
12e4743
fix(sync): isolate protected pytest execution
Jul 11, 2026
1ce5256
fix(sync): align verifier fixture with provenance policy
Jul 11, 2026
d31c2dd
fix(sync): keep runner contract lint-clean
Jul 11, 2026
ea0b5e6
test(sync): reproduce CLI candidate replay gap
Jul 11, 2026
1af7af6
fix(sync): wire candidate replay ledger in certify
Jul 11, 2026
99c9b13
test(sync): reproduce concurrent candidate replay race
Jul 11, 2026
09de192
fix(sync): make candidate replay ledger atomic
Jul 11, 2026
7d380c6
test(sync): reproduce historical provenance expiry
Jul 11, 2026
4533c87
fix(sync): validate historical provenance at observation time
Jul 11, 2026
a360620
test(sync): reproduce protected pytest closure gaps
Jul 11, 2026
7c91919
fix(sync): protect exact-head pytest closure
Jul 11, 2026
ae20b54
test(sync): reproduce unmeasured lifecycle runtime lock
Jul 11, 2026
0824dbd
fix(sync): bind measured lifecycle runtime inputs
Jul 11, 2026
638df99
fix(sync): keep hardened foundation lint-clean
Jul 11, 2026
88454de
test(sync): reproduce round-nine trust-boundary gaps
Jul 11, 2026
12ac35f
fix(sync): close provenance and support-closure gaps
Jul 11, 2026
34526c7
test(sync): reproduce sandbox loader and replay gaps
Jul 11, 2026
14aecf5
test(sync): require lifecycle measurement schema
Jul 11, 2026
5f8e57b
fix(sync): enforce verifiable execution and replay boundaries
Jul 11, 2026
520ffcb
test(sync): expose remaining verification boundary gaps
Jul 11, 2026
73f968b
fix(sync): require portable verifiable boundaries
Jul 11, 2026
0701524
test(sync): define protected replay root boundary
Jul 11, 2026
3b6a06b
fix(sync): anchor candidate replay storage at protected root
Jul 11, 2026
b9e154b
test(sync): expose round 11 trust boundary gaps
Jul 11, 2026
541fe7b
fix(sync): close round 11 verification boundaries
Jul 11, 2026
a905866
test(sync): expose round 13 verification boundaries
Jul 11, 2026
b61046f
fix(sync): close round 13 verification boundaries
Jul 11, 2026
48fb2a0
fix(sync): ignore reaped supervised processes
Jul 11, 2026
c8e8842
fix(sync): provision hosted runner sandbox with sudo
Jul 11, 2026
2cf43a1
test(sync): reproduce protected runner review gaps
Jul 11, 2026
f314804
fix(sync): close protected runner review gaps
Jul 11, 2026
ad5487d
fix(sync): repair Linux smoke workflow syntax
Jul 11, 2026
d7955ac
fix(sync): drop sandbox privileges after mounts
Jul 11, 2026
bb3e1a8
fix(sync): preopen private sandbox mounts
Jul 11, 2026
6f7e70a
fix(sync): stage private sandbox bind sources
Jul 11, 2026
6d9f1aa
fix(sync): create isolated mount ancestry
Jul 11, 2026
7e36c74
fix(sync): retain host uid in protected namespace
Jul 11, 2026
afa7322
fix(sync): preserve runtime loader mount aliases
Jul 11, 2026
b003364
fix(sync): mount exact protected repository inputs
Jul 11, 2026
ef3a5d4
fix(sync): mount checker collection probe
Jul 11, 2026
4fcba50
test(sync): add focused protected runner CI lane
Jul 11, 2026
13840cd
test(sync): reproduce next review boundary gaps
Jul 11, 2026
57e6e48
fix(sync): close next review trust boundaries
Jul 11, 2026
f4ffd05
test(sync): reproduce five review findings
Jul 12, 2026
aed3301
fix(sync): close five verification boundaries
Jul 12, 2026
e5c63c5
fix(sync): preserve worker outcomes across checker boundary
Jul 12, 2026
8d64c75
fix(sync): retain protected collection diagnostics
Jul 12, 2026
f8ea5fe
test(sync): reproduce exhaustive review findings
Jul 12, 2026
483cb80
fix(sync): close exhaustive verification boundaries
Jul 12, 2026
e0f0b82
fix(sync): separate candidate result channels
Jul 12, 2026
9c99a08
fix(sync): preserve legacy policy fast path
Jul 12, 2026
81b6552
test(sync): bound canonical policy activation
Jul 12, 2026
c219f97
ci: bound broad unit suite with measured margin
Jul 12, 2026
d6963ba
fix(sync): cache committed policy activation
Jul 12, 2026
1b08d03
test(sync): reproduce high review boundary gaps
Jul 12, 2026
064b41e
fix(sync): close high review verification boundaries
Jul 12, 2026
21fffbe
test(sync): reproduce CLI preflight state writes
Jul 12, 2026
d7a2f66
fix(sync): preflight CLI before state bookkeeping
Jul 12, 2026
51c2159
test(sync): reproduce remaining high review boundaries
Jul 12, 2026
e281fa7
fix(sync): close remaining high review boundaries
Jul 12, 2026
d03d4cf
fix(sync): preserve protected pytest diagnostics
Jul 12, 2026
dd87f77
test(sync): cover complete trusted runtime boundaries
Jul 12, 2026
1d5cf32
fix(sync): bind protected runtime and signer containment
Jul 12, 2026
589c9b2
fix(sync): mount post-drop sandbox executable
Jul 12, 2026
6aa15cf
fix(sync): preserve sandbox loader mount paths
Jul 12, 2026
b3dd5cf
fix(sync): tolerate unsupported host tool permissions
Jul 12, 2026
ba58dff
test(sync): gate signer containment on Linux
Jul 12, 2026
05ca6ff
fix(sync): preserve interpreter mount spelling
Jul 12, 2026
c8af869
perf(sync): cache measured runtime closure digest
Jul 12, 2026
b9167af
perf(sync): retain runtime byte-change invalidation
Jul 12, 2026
00f93af
perf(sync): cache released runtime path discovery
Jul 12, 2026
369a676
fix(sync): mount interpreter libraries and elevate signer scope
Jul 12, 2026
dc2b651
test(sync): cover resolved runtime library mounts
Jul 12, 2026
a08e6d3
fix(sync): bind resolved runtime dependencies
Jul 12, 2026
9187877
fix(sync): place signer readiness marker outside tmpfs
Jul 12, 2026
9b7e295
test(sync): cover measured loader search paths
Jul 12, 2026
c7a2f0a
fix(sync): expose measured loader directories
Jul 12, 2026
2712920
test(sync): prevent mocked runtime cache poisoning
Jul 12, 2026
efb7f20
fix(sync): preserve protected execution diagnostics
Jul 12, 2026
5b34e3b
fix(sync): retain checker diagnostics outside candidate files
Jul 12, 2026
fb82bc4
fix(sync): retain bound directory ancestry
Jul 12, 2026
8700a6b
fix(sync): bind resolved nested interpreters
Jul 12, 2026
2963efa
test(sync): cover interpreter sandbox mount destinations
Jul 12, 2026
9f07c4e
fix(sync): bind runtime paths at namespace spellings
Jul 12, 2026
89afb96
test(sync): cover limited command interpreter mount
Jul 12, 2026
50816e0
fix(sync): mount limited command interpreter
Jul 12, 2026
c15cbdd
test(sync): expose protected collection failures early
Jul 12, 2026
c016be9
perf(sync): hash measured runtime concurrently
Jul 12, 2026
204d385
fix(sync): expose trusted collection probe
Jul 12, 2026
8e3c863
fix(sync): create trusted junit channel before binding
Jul 12, 2026
d03115b
perf(sync): deduplicate measured runtime roots
Jul 12, 2026
4be27f9
fix(sync): restore protected imports and signer readiness
Jul 12, 2026
7baf292
fix(sync): stream contained signer diagnostics
Jul 12, 2026
91e186c
fix(sync): retain signer timeout diagnostics
Jul 12, 2026
2f5139a
test(sync): isolate protected signer containment smoke
Jul 12, 2026
a2424de
test(sync): preserve signer startup failure diagnostics
Jul 12, 2026
3da5fdd
fix(sync): expose signer readiness after tmp isolation
Jul 12, 2026
4ff7c61
test(sync): verify detached signer by host pid
Jul 12, 2026
2d48424
test(sync): detect detached signer by command marker
Jul 12, 2026
e54147b
fix(sync): close pytest shadow and replay aliases
Jul 12, 2026
48dd662
fix(sync): preload trusted collection probe
Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ jobs:
if: github.event.pull_request.draft != true
name: Run Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 30
# The measured green broad suite takes ~30 minutes; retain bounded 50% headroom.
timeout-minutes: 45

env:
PDD_PATH: ${{ github.workspace }}/pdd
Expand All @@ -55,6 +56,83 @@ jobs:
- name: Install dependencies
run: pip install -e ".[dev]" pytest-timeout

- name: Provision and verify protected Linux sandbox
run: |
sudo apt-get update
sudo apt-get install --yes bubblewrap
private_root="$(mktemp -d)"
chmod 700 "$private_root"
mkdir -m 700 "$private_root/scratch"
PRIVATE_ROOT="$private_root" python - <<'PY'
import os
import json
import subprocess
import sys
from pathlib import Path
from pdd.sync_core.supervisor import (
_sandbox_command,
released_runtime_closure_paths,
run_supervised,
)

root = Path(os.environ["PRIVATE_ROOT"])
resolved_interpreter = Path(sys.executable).resolve()
print("resolved interpreter:", resolved_interpreter)
print(subprocess.run(
["ldd", str(resolved_interpreter)], check=False, text=True,
capture_output=True,
).stdout)
sandbox, _ = _sandbox_command(
[sys.executable, "-c", "import math"], (root / "scratch",)
)
print("sandbox destinations:", json.loads(sandbox[-2]))
print("runtime native paths:", [
str(path) for label, path in released_runtime_closure_paths()
if label.startswith("native/")
])
script = """from pathlib import Path
import math
Path('ok').write_text('ok')
try:
Path('../outside').write_text('forbidden')
except OSError:
pass
else:
raise SystemExit('write escaped private scratch root')
"""
result, surviving = run_supervised(
[sys.executable, "-c", script],
cwd=root / "scratch", timeout=10, env=dict(os.environ),
writable_roots=(root / "scratch",),
)
assert result.returncode == 0, result.stderr
assert not surviving
assert (root / "scratch" / "ok").read_text() == "ok"
assert not (root / "outside").exists()
PY

- name: Verify protected pytest smoke
run: >
pytest -q
tests/test_sync_core_runner.py::test_passing_collected_test_is_pass
--timeout=60

- name: Verify protected signer containment smoke
run: >
pytest -q
tests/test_sync_core_trust.py::test_remote_signer_timeout_reaps_env_cleared_detached_descendant
--timeout=60

- name: Run focused protected-runner tests
run: >
pytest -q
tests/test_sync_core_supervisor.py
tests/test_sync_core_runner.py
tests/test_sync_core_trust.py
tests/test_sync_core_lifecycle_scenarios.py
tests/test_sync_core_reporting.py
--timeout=60

- name: Validate architecture vs prompt includes (fixture smoke)
run: pdd checkup --validate-arch-includes --project-root tests/fixtures/arch_include_validate_ok

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ yarn.lock
# PDD internal directory (backups, locks, worktrees, etc.)
.pdd/*
!.pdd/
!.pdd/repository-id
!.pdd/meta/
.pdd/meta/*.json
!.pdd/meta/agentic_bug_orchestrator_python.json
Expand Down
1 change: 1 addition & 0 deletions .pdd/repository-id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3b4d7b1c-d6cc-4752-ba93-6b98d1a710e0
1,659 changes: 1,659 additions & 0 deletions docs/global_sync_resolution_plan.md

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions pdd/auto_deps_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def auto_deps_main(
("", 0.0, f"Error: {exc}") on non-Abort failures so orchestrators can
continue gracefully.
"""
from .sync_core.finalize import preflight_legacy_mutation
preflight_legacy_mutation({"prompt": Path(prompt_file)})
console = Console()
quiet = ctx.obj.get("quiet", False) if ctx.obj else False

Expand Down Expand Up @@ -254,12 +256,18 @@ def auto_deps_main(
model=model_name,
)
except Exception as fp_exc:
from .sync_core.finalize import CanonicalFinalizationError
if isinstance(fp_exc, CanonicalFinalizationError):
raise
if not quiet:
console.print(
f"[yellow]Warning: Failed to save fingerprint for "
f"{basename}_{language}: {fp_exc}[/yellow]"
)
except Exception as meta_exc:
from .sync_core.finalize import CanonicalFinalizationError
if isinstance(meta_exc, CanonicalFinalizationError):
raise
# Never mask a successful auto-deps result on metadata errors
if not quiet:
console.print(
Expand All @@ -272,6 +280,9 @@ def auto_deps_main(
# Re-raise to allow orchestrators (e.g. pdd sync) to stop the loop
raise
except Exception as exc:
from .sync_core.finalize import CanonicalFinalizationError
if isinstance(exc, CanonicalFinalizationError):
raise
if not quiet:
console.print(f"[red]Error in auto-deps: {exc}[/red]")
return "", 0.0, f"Error: {exc}"
107 changes: 96 additions & 11 deletions pdd/ci_drift_heal.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import argparse
import csv
import json
import math
import os
import re
Expand Down Expand Up @@ -57,6 +58,46 @@ def _heal_subprocess_timeout() -> int:
_INVARIANT_KEYS = {"include", "pdd_tags", "percent_markers", "fenced_blocks"}


def _dry_run_json_summary(report: Dict[str, Any]) -> Dict[str, Any]:
"""Return the fixed dry-run schema without paths or diagnostic payloads."""
summary = report.get("summary")
if not isinstance(summary, dict):
summary = {}

def count(name: str) -> int:
value = summary.get(name)
valid_count = (
isinstance(value, int) and not isinstance(value, bool) and value >= 0
)
return value if valid_count else 0

units = report.get("units")
if not isinstance(units, list):
units = []
projected_units = []
for unit in units:
if not isinstance(unit, dict):
continue
identity = {
name: unit.get(name)
for name in ("basename", "language", "classification")
}
if all(isinstance(value, str) and value for value in identity.values()):
projected_units.append(identity)

return {
"ok": report.get("ok") is True,
"consumer": "ci-heal",
"summary": {
"metadata_stale": count("metadata_stale"),
"conflicts": count("conflicts"),
"unbaselined": count("unbaselined"),
"failures": count("failures"),
},
"units": projected_units,
}


# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -299,25 +340,29 @@ def _discover_modules() -> List[Tuple[str, str, Any]]:
"""Discover (basename, language, prompt_path) tuples."""
try:
from pdd.user_story_tests import discover_prompt_files
except ImportError:
return []
except ImportError as exc:
raise RuntimeError("cannot import prompt discovery") from exc
try:
prompt_files = discover_prompt_files()
except Exception:
return []
except Exception as exc:
raise RuntimeError("prompt discovery failed") from exc

try:
from pdd.operation_log import infer_module_identity
except ImportError:
return []
except ImportError as exc:
raise RuntimeError("cannot import canonical module identity") from exc

out: List[Tuple[str, str, Any]] = []
failures: List[str] = []
for entry in prompt_files:
try:
basename, language = infer_module_identity(str(entry))
except Exception:
except Exception as exc:
failures.append(f"{entry}: {exc}")
continue
out.append((basename, language, entry))
if failures:
raise RuntimeError("module identity failed: " + "; ".join(failures))
return out


Expand Down Expand Up @@ -412,6 +457,8 @@ def detect_drift(
changed_files = _get_git_changed_files(diff_base)

discovered = _discover_modules()
if not discovered and parsed is None:
raise RuntimeError("no synchronization units were discovered")
if parsed is not None:
wanted = set(parsed)
discovered = [t for t in discovered if t[0] in wanted]
Expand Down Expand Up @@ -459,8 +506,10 @@ def detect_drift(
decision = sync_determine_operation(
basename, language, target_coverage=90.0, log_mode=True
)
except Exception:
continue
except Exception as exc:
raise RuntimeError(
f"classification failed for {basename}/{language}: {exc}"
) from exc

op = _extract_op(decision)
if not op or op in ("nothing", "synced"):
Expand Down Expand Up @@ -1616,6 +1665,12 @@ def heal_module(drift: DriftInfo, env: Dict[str, str]) -> Optional[bool]:
label=f"pdd sync {drift.basename}",
)
return ok if ok else False
if op == "fail_and_request_manual_merge":
console.print(
f"[yellow]manual merge required for {drift.basename}; "
"skipping auto-heal[/yellow]"
)
return None

console.print(f"[red]unknown operation '{op}' for {drift.basename}[/red]")
return False
Expand Down Expand Up @@ -1848,13 +1903,43 @@ def main(
as_json: bool = False,
) -> int:
"""Detect drift, heal modules, and commit healed changes."""
from pdd.continuous_sync import canonical_sync_enabled

if canonical_sync_enabled(Path.cwd()) and not dry_run:
from pdd.sync_core import CanonicalReportOptions, build_canonical_report

base_ref = (
diff_base.split("...", 1)[0]
if diff_base and "..." in diff_base
else "HEAD"
)
try:
canonical = build_canonical_report(
_repo_root(),
CanonicalReportOptions(
base_ref=base_ref,
head_ref="HEAD",
modules=tuple(modules or ()),
),
)
except (OSError, RuntimeError, ValueError) as exc:
console.print(f"[red]canonical sync check failed: {exc}[/red]")
return 1
if not canonical["ok"]:
if as_json:
print(json.dumps(canonical, indent=2, sort_keys=True))
else:
console.print("[red]canonical sync predicate failed[/red]")
for error in canonical.get("errors", []):
console.print(f"[red]- {error}[/red]")
return 1
return 0
if dry_run:
from pdd.continuous_sync import build_report
import json as _json

report = build_report(consumer="ci-heal", modules=modules)
if as_json:
print(_json.dumps(report, indent=2, sort_keys=True))
print(json.dumps(_dry_run_json_summary(report), indent=2, sort_keys=True))
else:
summary = report["summary"]
console.print(
Expand Down
5 changes: 5 additions & 0 deletions pdd/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .firecrawl import firecrawl_cache
from .story import story_cli
from .reconcile import install_hooks, reconcile
from .sync_core import baseline, certify, recover, validate

def register_commands(cli: click.Group) -> None:
"""Register all subcommands with the main CLI group."""
Expand Down Expand Up @@ -55,6 +56,10 @@ def register_commands(cli: click.Group) -> None:
cli.add_command(which)
cli.add_command(reconcile)
cli.add_command(install_hooks)
cli.add_command(certify)
cli.add_command(recover)
cli.add_command(baseline)
cli.add_command(validate)

# Register templates group directly to commands dict to handle nesting if needed,
# or just add_command works for groups too.
Expand Down
9 changes: 9 additions & 0 deletions pdd/commands/maintenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,15 @@ def _restore_model_default(_prev=_prev_model_default):
effective_compressed_context = _resolve_compressed_context(compressed_context)
ctx.obj["compressed_context"] = effective_compressed_context

if not dry_run:
from ..sync_core.finalize import preflight_legacy_mutation

try:
preflight_legacy_mutation()
except RuntimeError as exc:
ctx.obj["_suppress_core_dump"] = True
raise click.UsageError(str(exc)) from exc

# No basename -> global Tier 1 sync
if basename is None:
if snapshot_context:
Expand Down
9 changes: 7 additions & 2 deletions pdd/commands/reconcile.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def _emit_report(report: dict, as_json: bool) -> None:
"--heal",
is_flag=True,
default=False,
help="Refresh valid stale fingerprints without LLM calls.",
help="Deprecated; blind fingerprint acceptance is disabled.",
)
@click.option(
"--ledger",
Expand Down Expand Up @@ -103,10 +103,15 @@ def reconcile(
ctx.ensure_object(dict)
if as_json:
ctx.obj["_suppress_result_summary"] = True
if heal:
raise click.UsageError(
"--heal is disabled because changing a fingerprint does not prove "
"semantic synchronization; run pdd sync/update/resolve instead"
)
report = build_report(
consumer="reconcile",
modules=[module_name] if module_name else None,
heal=heal,
heal=False,
ledger=ledger,
)
_emit_report(report, as_json)
Expand Down
Loading
Loading