Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
234 commits
Select commit Hold shift + click to select a range
ba51230
test(sync): reproduce missing trusted Playwright adapter
Jul 11, 2026
e75b14f
fix(sync): add trusted Playwright verification adapter
Jul 11, 2026
398bdda
test(sync): reproduce Playwright protected clone dependency gap
Jul 11, 2026
72cb8ae
test(sync): exercise Playwright clone dependency resolution
Jul 11, 2026
2df2361
fix(sync): resolve Playwright protected clone dependencies
Jul 11, 2026
a58932c
test(sync): clean Playwright adapter lint import
Jul 11, 2026
28d17ca
test(sync): reproduce Playwright relative result path gap
Jul 11, 2026
7df48d7
fix(sync): parse Playwright relative result paths
Jul 11, 2026
783cc49
fix(sync): parse Playwright relative result paths
Jul 11, 2026
8d41353
test(sync): reproduce Playwright isolated browser cache gap
Jul 11, 2026
37dd166
fix(sync): use pinned Playwright browser cache
Jul 11, 2026
afccf9a
test: cover Playwright trust boundary gaps
Jul 11, 2026
546ae18
fix: reject unbound Playwright toolchains
Jul 11, 2026
dc2ee04
test: cover remaining Playwright trust gaps
Jul 11, 2026
7bdf6df
fix: harden Playwright command and config trust
Jul 11, 2026
d6c8481
test(sync): cover Playwright round three gaps
Jul 11, 2026
7ae2a02
fix(sync): wire protected Playwright validation CLI
Jul 11, 2026
4c7efe5
fix(sync): bind Playwright JS support candidates
Jul 11, 2026
7ab31c1
fix(sync): reject pathless Playwright validator operands
Jul 11, 2026
7f24cff
test(sync): cover Playwright dependency binding gaps
Jul 11, 2026
9df884e
fix(sync): bind Playwright dependency environment
Jul 11, 2026
2df310f
test(sync): cover Playwright round seven trust gaps
Jul 11, 2026
b5df333
test(sync): reject Playwright prefix preload options
Jul 11, 2026
c49407d
fix(sync): isolate Playwright trust descriptors
Jul 11, 2026
d171597
fix(sync): retain Playwright trust checks before isolation
Jul 11, 2026
5730d1a
test(sync): cover Playwright round eight trust gaps
Jul 11, 2026
a63865a
fix(sync): close Playwright round eight trust gaps
Jul 11, 2026
19513cb
test(sync): cover Playwright round nine trust gaps
Jul 11, 2026
fe56c0a
fix(sync): close Playwright round nine trust gaps
Jul 11, 2026
11baf64
test(sync): cover Playwright AST and identity trust boundary
Jul 11, 2026
7f33042
test(sync): cover immutable Playwright protocol identity
Jul 11, 2026
f105317
fix(sync): enforce Playwright AST and immutable toolchain closure
Jul 11, 2026
a2ca38c
fix(sync): synchronize Playwright parser dependencies
Jul 11, 2026
29d7de0
test(sync): cover Playwright round eleven trust gaps
Jul 11, 2026
cb4df05
fix(sync): close Playwright round eleven trust gaps
Jul 11, 2026
bb068ae
test(sync): cover Playwright round twelve trust gaps
Jul 11, 2026
0f1e909
fix(sync): close Playwright round twelve trust gaps
Jul 11, 2026
8642f0b
test(sync): cover Playwright round thirteen gaps
Jul 11, 2026
7d937d9
fix(sync): close Playwright round thirteen gaps
Jul 11, 2026
1a57111
test(sync): cover Playwright round fourteen gaps
Jul 11, 2026
02c84f3
fix(sync): close Playwright round fourteen gaps
Jul 11, 2026
2ec5acc
fix(sync): preserve Git mode text in Playwright integration
Jul 13, 2026
c751121
fix(sync): share pinned JavaScript parsers across adapters
Jul 13, 2026
479d659
fix(sync): keep Playwright runner lint-clean
Jul 13, 2026
c8ea533
test(sync): align Playwright contracts with shared runner
Jul 13, 2026
5c060d3
fix(sync): harden protected Playwright adapter
Jul 13, 2026
2462b1c
fix(sync): complete Playwright runner contracts
Jul 13, 2026
37ae201
test(sync): exercise real protected Playwright lanes
Jul 13, 2026
80badaa
fix(sync): deduplicate protected runtime mounts
Jul 13, 2026
75faa61
fix(sync): constrain Playwright Node virtual memory
Jul 13, 2026
4a82459
test(sync): surface protected browser failures
Jul 13, 2026
dabbefe
test(sync): preserve browser failure tail
Jul 13, 2026
0c22493
fix(sync): bound Chromium trap-handler configuration
Jul 13, 2026
f63a077
fix(sync): budget Chromium virtual address space
Jul 13, 2026
eaac1b4
fix(sync): accommodate Chromium address reservation
Jul 13, 2026
5210858
fix(sync): provide bounded Playwright temp space
Jul 13, 2026
26fde8c
fix(sync): use namespace-local Playwright temp path
Jul 13, 2026
30c018a
fix(sync): order nested sandbox mounts correctly
Jul 13, 2026
1ae73ea
fix(sync): isolate Playwright sandbox temp mounts
Jul 13, 2026
33a9842
test(sync): cover private Playwright config overlay
Jul 14, 2026
08faa2f
fix(sync): inject Playwright browser flag privately
Jul 14, 2026
6cd272d
test(sync): cover private overlay hardening
Jul 14, 2026
fbd9a8f
fix(sync): harden private Playwright overlay
Jul 14, 2026
35acb4f
test(sync): define cgroup Playwright resource contracts
Jul 14, 2026
5ae11df
fix(sync): contain validators with cgroup v2
Jul 14, 2026
6bdafc7
test(sync): require authoritative systemd scope containment
Jul 14, 2026
261cc54
fix(sync): verify transient systemd scope containment
Jul 14, 2026
b010c28
fix(sync): handle prelaunch scope failures
Jul 14, 2026
0e2d9cb
refactor(sync): trim transient scope plan
Jul 14, 2026
02d60b8
test(sync): reproduce hosted Playwright and quota failures
Jul 14, 2026
1783331
fix(sync): close Playwright and writable result races
Jul 14, 2026
a72d4e6
test(sync): account for trusted ESM dependency binding
Jul 14, 2026
a0c8425
test(sync): require kernel writable and package isolation
Jul 14, 2026
8681482
fix(sync): enforce writable and package boundaries
Jul 14, 2026
f330c09
fix(sync): satisfy supervisor lint structure
Jul 14, 2026
d10704c
test(sync): require transactional outputs and node trust
Jul 14, 2026
680a112
fix(sync): publish outputs and bind Node trust
Jul 14, 2026
53d2d60
test(sync): cover executable Node search paths
Jul 14, 2026
b822418
refactor(sync): split writable publication transaction
Jul 14, 2026
fccb943
test(sync): require private results and full Node trust
Jul 14, 2026
ef02c2f
fix(sync): isolate results and enforce full Node trust
Jul 14, 2026
1f82a4e
test(sync): align transient helper contract
Jul 14, 2026
e51ede4
test(sync): define verification assurance boundary
Jul 14, 2026
0cb422c
fix(sync): enforce verification assurance levels
Jul 14, 2026
605c442
docs(sync): define framework assurance boundary
Jul 14, 2026
078d9e0
test(sync): restamp assurance-bound profile digest
Jul 14, 2026
058bc1a
refactor(sync): isolate assurance reconciliation
Jul 14, 2026
2de6b99
test(sync): reject invalid profile reconciliation
Jul 14, 2026
074bb5d
fix(sync): block invalid profile evidence reuse
Jul 14, 2026
59d92cc
test(sync): cover portable observations and timeout precedence
Jul 14, 2026
2382992
fix(sync): make framework observations portable
Jul 14, 2026
14d2b66
fix(sync): describe framework observations accurately
Jul 14, 2026
40a31a0
test(sync): reserve timeout status for proven workloads
Jul 14, 2026
47438ce
fix(sync): prove candidate execution timeouts
Jul 14, 2026
6ed4cf0
test(sync): cover completed workload proof race
Jul 14, 2026
eef407b
fix(sync): preserve completed workload proofs
Jul 14, 2026
0b8536e
test(sync): move timeout authority into privileged helper
Jul 14, 2026
a8744c1
fix(sync): make privileged helper own candidate deadline
Jul 14, 2026
b72a8b1
test(sync): cover trusted timeout failure boundaries
Jul 14, 2026
5394b67
fix(sync): harden trusted timeout teardown evidence
Jul 14, 2026
0e3d558
test(sync): cover hosted Linux staging regressions
Jul 14, 2026
13a27e9
fix(sync): repair hosted Linux staging order
Jul 14, 2026
68ce4ad
test(sync): reproduce CPython venv alias staging
Jul 14, 2026
87fb4a1
fix(sync): normalize supervised CPython venv tree
Jul 14, 2026
fb9bd40
test(sync): require atomic lifecycle environment transaction
Jul 14, 2026
db1016d
fix(sync): keep candidate lifecycle in one transaction
Jul 14, 2026
103a2d7
test(sync): require OOM-surviving cgroup monitor leaf
Jul 14, 2026
f5d3c91
fix(sync): isolate cgroup monitor from candidate OOM
Jul 14, 2026
5d47596
test(sync): cover Playwright list reporter protocol
Jul 14, 2026
6c608d7
fix(sync): canonicalize Playwright list identities
Jul 14, 2026
791c081
test(sync): reject partial Playwright reporter receipts
Jul 14, 2026
a864bb1
fix(sync): latch Playwright reporter errors
Jul 14, 2026
f3e7c95
test(sync): reproduce inaccessible delegated cgroup leaf
Jul 14, 2026
1068aa6
fix(sync): expose delegated leaf state to coordinator
Jul 14, 2026
58c2b7d
test(sync): diagnose Playwright reporter rejection
Jul 14, 2026
a589248
fix(sync): diagnose Playwright reporter invariants
Jul 14, 2026
f1e2739
test(sync): distinguish Playwright callback failures
Jul 14, 2026
0e93cb9
fix(sync): honor Playwright final reporter status
Jul 14, 2026
3c3b44d
test(sync): classify Playwright read-only framework errors
Jul 14, 2026
ec8b756
fix(sync): classify Playwright framework filesystem errors
Jul 14, 2026
432f481
test(sync): require Node 26 for protected Playwright
Jul 14, 2026
2aa78bf
fix(ci): use Node 26 for protected Playwright
Jul 14, 2026
ebd857d
fix(sync): remove unverified framework error categories
Jul 14, 2026
cb44046
test(sync): require Node 24 LTS for protected Playwright
Jul 14, 2026
778695a
fix(ci): use Node 24 LTS for protected Playwright
Jul 14, 2026
dc824e2
fix(ci): remove disproven Playwright runtime workaround
Jul 14, 2026
8810b52
test(sync): classify bounded Playwright framework errors
Jul 14, 2026
49cea0a
fix(sync): classify bounded Playwright framework errors
Jul 14, 2026
b3172e9
fix(sync): remove inconclusive framework error categories
Jul 14, 2026
d0324ec
test(sync): fingerprint controlled Playwright framework errors
Jul 14, 2026
287ec59
test(sync): map controlled Playwright config-load causes
Jul 14, 2026
ee9d06f
fix(sync): remove inconclusive config-load diagnostics
Jul 14, 2026
3a0c2cd
test(sync): reveal controlled Playwright config error
Jul 14, 2026
08b7d18
fix(sync): remove controlled config diagnostic
Jul 14, 2026
e069f37
test(sync): require absolute protected Playwright test paths
Jul 14, 2026
a7e6563
fix(sync): use absolute protected Playwright test paths
Jul 14, 2026
465baaf
test(sync): inspect controlled Playwright discovery state
Jul 14, 2026
2d96673
test(sync): capture controlled Playwright error sequence
Jul 14, 2026
20aab6b
test(sync): split protected Playwright file filtering
Jul 14, 2026
1303be0
test(sync): capture no-filter Playwright load errors
Jul 14, 2026
3211082
fix(sync): preserve canonical Playwright package identity
Jul 14, 2026
1b945d9
test(sync): require canonical Playwright topology and exact filters
Jul 14, 2026
30fb6fb
fix(sync): canonicalize protected Playwright package topology
Jul 14, 2026
d5d2a6c
test(sync): require bounded Playwright renderer address space
Jul 14, 2026
a47c16e
fix(sync): budget protected Playwright renderer address space
Jul 14, 2026
226b00a
test(ci): require full unit job timeout budget
Jul 14, 2026
fd99236
fix(ci): budget full unit test job duration
Jul 14, 2026
f8b3a6f
test(sync): cover bound synthetic Playwright entrypoint
Jul 14, 2026
0e30ed3
fix(sync): resolve synthetic Playwright entrypoint bindings
Jul 14, 2026
ee90f2d
test(sync): require unit workflow ownership preauthorization
Jul 14, 2026
0baa97e
test(sync): cover declared loader mount precedence
Jul 14, 2026
9dcb98b
fix(sync): prioritize declared native runtime mounts
Jul 14, 2026
9f4ce53
test(sync): reject unproven copied runtime binding
Jul 15, 2026
adad2e3
fix(sync): prove copied runtime binding identity
Jul 15, 2026
6e2abc2
test(sync): recheck copied runtime proof before staging
Jul 15, 2026
e8d619e
test(sync): expose copied runtime proof authority gaps
Jul 15, 2026
58fdeea
test(sync): bind proof to native runtime topology
Jul 15, 2026
3ba36ef
fix(sync): close immutable runtime staging authority gaps
Jul 15, 2026
d676a15
test(sync): cover canonical immutable snapshot modes
Jul 15, 2026
0fa5d65
fix(sync): preserve immutable snapshot descriptor mode
Jul 15, 2026
015a791
test(sync): cover candidate-owned runtime snapshots
Jul 15, 2026
4c3c1e0
fix(sync): bind runtime snapshots to candidate ownership
Jul 15, 2026
738f7d2
Merge remote-tracking branch 'origin/main' into codex/issue-1932-play…
Jul 15, 2026
c6e9022
fix(sync): stage protected observation fifo
Jul 15, 2026
c2f72f2
fix(sync): preserve nested toolchain mounts
Jul 15, 2026
4228037
Merge remote-tracking branch 'origin/main' into codex/issue-1932-play…
Jul 15, 2026
e6f2109
test(sync): align candidate runtime integration contract
Jul 15, 2026
3f1a575
fix(sync): harden privileged playwright staging
Jul 15, 2026
11e93e3
fix(sync): isolate privileged probe interpreter
Jul 15, 2026
34ec1a3
fix(sync): bind exact Playwright launcher snapshot
Jul 15, 2026
09c6715
fix(sync): bind playwright snapshots to accepted identity
Jul 15, 2026
54066ed
fix(sync): preserve playwright entrypoint identity contract
Jul 15, 2026
831619f
fix(sync): enforce privileged playwright aggregate
Jul 15, 2026
7e3bf40
fix(sync): bind privileged observation authority
Jul 15, 2026
550dbe6
merge(sync): reconcile trusted runner supervision
Jul 15, 2026
a4a307d
fix: harden Playwright descriptor supervision
Jul 15, 2026
244f363
Merge remote-tracking branch 'origin/main' into codex/issue-1932-play…
Jul 15, 2026
dc97639
fix(sync): account immutable staging bytes
Jul 15, 2026
185f2b0
fix(sync): harden descriptor environment protocol
Jul 15, 2026
9f94613
Merge remote-tracking branch 'origin/main' into codex/issue-1932-play…
Jul 15, 2026
2719540
test(sync): verify hosted descriptor cleanup
Jul 15, 2026
7527760
test(sync): close hosted cleanup review gaps
Jul 15, 2026
4aa09c6
test(sync): prove descriptor blocked-state cleanup
Jul 15, 2026
07dac9d
test(sync): prove privileged fallback cleanup
Jul 15, 2026
7b4e36a
test(sync): bind fallback cleanup ownership
Jul 15, 2026
997fec3
test(sync): bound Playwright launch descriptor argv
Jul 15, 2026
8932f03
fix(sync): stream privileged launch authority
Jul 15, 2026
5ae63f9
test(sync): enforce launch frame lifecycle
Jul 15, 2026
4b007a2
Merge remote-tracking branch 'origin/main' into codex/issue-1932-play…
Jul 15, 2026
3fc9708
test(sync): reproduce hosted Linux runtime failures
Jul 15, 2026
ad6363b
fix(sync): restore hosted Linux sandbox compatibility
Jul 15, 2026
cf01743
fix(sync): bind protected helper runtime
Jul 15, 2026
f5e0b9b
fix(sync): isolate candidate cgroup lifecycle
Jul 15, 2026
14648b0
test(sync): expose missing cgroup termination telemetry
gltanaka Jul 15, 2026
2f3eb2b
test(sync): reproduce sibling cgroup namespace migration
gltanaka Jul 15, 2026
82c1671
fix(sync): preserve cgroup termination telemetry
gltanaka Jul 15, 2026
18bb6c4
test(sync): reproduce hosted helper fork hazards
gltanaka Jul 15, 2026
48c7318
fix(sync): launch sandbox drains after trusted fork
gltanaka Jul 15, 2026
3c719de
Merge pull request #2092 from promptdriven/fix/2091-hosted-boundary
gltanaka Jul 15, 2026
373cc0d
Merge commit '79c445de7c87d88879c7be8352a679254c10a3b1' into integrat…
gltanaka Jul 15, 2026
e542458
fix: deduplicate workflow ownership rule
gltanaka Jul 15, 2026
6989081
fix(sync): inherit hosted workflow preauthorization
gltanaka Jul 15, 2026
58beab3
fix(sync): arm candidate cgroup after namespace entry
Jul 15, 2026
391a9e8
Revert "fix(sync): arm candidate cgroup after namespace entry"
gltanaka Jul 15, 2026
82778c8
test(sync): reproduce authority token aliasing
gltanaka Jul 15, 2026
d18819d
fix(sync): separate supervision authority tokens
gltanaka Jul 15, 2026
3eb253a
test(sync): reproduce kernel-sized descriptor stall
gltanaka Jul 15, 2026
9abf98d
fix(sync): saturate stalled descriptor fixture
gltanaka Jul 15, 2026
79391ab
test(sync): reproduce descriptor observer race
gltanaka Jul 15, 2026
1108a58
fix(sync): synchronize descriptor lifecycle observation
gltanaka Jul 15, 2026
3b014c7
Revert "fix(sync): synchronize descriptor lifecycle observation"
gltanaka Jul 15, 2026
73b2b37
Revert "test(sync): reproduce descriptor observer race"
gltanaka Jul 15, 2026
1a8629d
test(sync): reproduce exhaustive initial lifecycle scan
gltanaka Jul 15, 2026
5f455e8
fix(sync): bound initial lifecycle observation to scope
gltanaka Jul 15, 2026
ca60bd4
test(sync): reject stale fallback scanner arguments
gltanaka Jul 15, 2026
33e9c76
fix(sync): preserve bounded fallback scan contract
gltanaka Jul 15, 2026
27a3cb5
test(sync): reproduce unheld descriptor lifecycle
gltanaka Jul 16, 2026
fcc185d
fix(sync): hold authenticated descriptor lifecycle
gltanaka Jul 16, 2026
6308506
test(sync): reproduce sparse holder termination race
gltanaka Jul 16, 2026
57a28ff
fix(sync): bind external cleanup to pidfd identity
gltanaka Jul 16, 2026
188c833
fix(sync): enter held namespace root for cleanup
Jul 16, 2026
1d4e4b8
fix(sync): pin fd holder root identity
Jul 16, 2026
b072736
fix(sync): normalize fdinfo mount IDs
Jul 16, 2026
34d5054
test(sync): require positive root mount ID
Jul 16, 2026
e4f20a6
test(sync): diagnose held namespace cleanup
Jul 16, 2026
2b07834
test(sync): correct held namespace diagnostics
Jul 16, 2026
4c62e17
test(sync): require sealed diagnostic transport
Jul 16, 2026
48ceab5
test(sync): model sealed held namespace scan frame
Jul 16, 2026
385ab88
test(sync): fix sealed transport fixture execution
Jul 16, 2026
b33732b
test(sync): bound held namespace Linux smoke
Jul 16, 2026
628c79f
test(sync): harden held namespace smoke contract
Jul 16, 2026
2e85b67
test(sync): tolerate transient fd directory closure
Jul 16, 2026
aba6cd0
test(sync): classify held namespace scan failures
Jul 16, 2026
4f4e71c
test(sync): tighten held namespace failure checks
Jul 16, 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
366 changes: 357 additions & 9 deletions .github/workflows/unit-tests.yml

Large diffs are not rendered by default.

39 changes: 38 additions & 1 deletion docs/global_sync_resolution_plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,39 @@ class VerificationObligation:
class VerificationProfile:
unit_id: UnitId
obligations: tuple[VerificationObligation, ...]
required_requirement_ids: tuple[str, ...]
profile_digest: str
assurance: AssuranceLevel = AssuranceLevel.STANDARD_FRAMEWORK
```

`AssuranceLevel` is an ordered, digest-bound profile property:

- `standard_framework` is the compatibility default. It assumes the selected
pytest, Jest, Vitest, or Playwright framework and its in-process hooks report
honestly. The checker creates, owns, and bounds the FIFO/file-descriptor framework
observation transport. It is candidate-visible under `standard_framework` and is
not authenticated evidence against candidate code in the same address space and
descriptor table.
- `isolated_black_box` is stronger. It requires candidate code to execute only as
an external SUT behind a process boundary that cannot mutate checker state or its
observation channel. No current in-process framework adapter satisfies this
assurance.

Protected base/head reconciliation takes the stronger assurance level and records
an attempted downgrade as invalid. Assurance is included in `profile_digest`, so
evidence cannot be replayed across assurance levels. Until an external SUT adapter
is implemented, an in-process adapter selected by an `isolated_black_box` profile
returns a deterministic non-pass result and the unit remains semantic `UNKNOWN`;
it cannot issue a passing result for that obligation.

This boundary is fundamental rather than a missing FIFO, proxy, seccomp, or
cryptographic feature. Hostile code executing inside the test framework's process
can alter framework callbacks, memory, and inherited descriptors before the
checker observes them. Signing the resulting statement authenticates who signed
it and what was bound, but cannot retroactively make the in-process observation
Byzantine-resistant. The isolated-black-box follow-up therefore requires a truly
out-of-process adapter, not another in-process reporter transport.

The profile accounts for every structured prompt requirement/contract identifier,
declared interface, required story/example, PDD-owned and human-owned validation
test, policy/security check, and dependency-closure obligation. A completeness
Expand Down Expand Up @@ -1132,7 +1162,9 @@ Tasks:
- Deploy the evidence trust plane before PR 13:
- Protected-base/control-plane `AttestationTrustPolicy` loader and verifier.
- Post-validation signer using dedicated workload identity and no candidate code.
- Authenticated trusted-runner result channel plus a nonce/check-run authority.
- Checker-owned bounded framework-observation transport for standard-framework
adapters, plus an isolated external observation boundary before any
isolated-black-box adapter is supported.
- Transparency/audit record store and evidence cache invalidation service.
- Threshold protected human-review attestation workflow for non-machine-verifiable
obligations.
Expand Down Expand Up @@ -1583,6 +1615,11 @@ The tracking epic records owner, PR, state, and exit-gate evidence for every row

## 11. Definition of Done

The global predicate described below is not currently achieved. In particular,
standard-framework observations do not satisfy a Byzantine-resistant
isolated-black-box claim, the external SUT adapter does not yet exist, and the
release/nightly evidence gates remain outstanding.

The global sync epic may close only when all conditions below hold with attached
commands, commit SHAs, and reports.

Expand Down
78 changes: 78 additions & 0 deletions pdd/commands/sync_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
from ..sync_core.runner import (
RunnerConfig,
_load_vitest_toolchain_descriptor,
_playwright_toolchain_identity,
_protected_command_error,
_playwright_command_error,
_vitest_command_error,
)
from .. import __version__
Expand Down Expand Up @@ -166,9 +168,29 @@ def _protected_command(value: str | None, option: str, cwd: Path) -> tuple[str,
return command


def _protected_playwright_command(
value: str | None, cwd: Path
) -> tuple[str, ...] | None:
"""Parse the fixed executable-plus-entrypoint Playwright descriptor."""
command = _protected_command(value, "--playwright-command", cwd)
if command is None:
return None
if len(command) != 2 or not Path(command[1]).expanduser().is_absolute():
raise click.ClickException(
"--playwright-command must contain exactly an absolute executable "
"and absolute external CLI entrypoint"
)
if any(part.startswith("-") for part in command[1:]):
raise click.ClickException(
"--playwright-command must not contain interpreter options"
)
return command


def _runner_config_from_options(
options: dict[str, object], cwd: Path
) -> RunnerConfig:
# pylint: disable=too-many-locals
"""Build trusted validator configuration from protected CLI/env options."""
jest_command = options.get("jest_command")
vitest_command = options.get("vitest_command")
Expand All @@ -194,6 +216,24 @@ def _runner_config_from_options(
error = _vitest_command_error(cwd, protected_vitest)
if error is not None:
raise click.ClickException(f"--vitest-command: {error}")
playwright_command = options.get("playwright_command")
playwright_manifest = options.get("playwright_toolchain_manifest")
protected_playwright = _protected_playwright_command(
playwright_command if isinstance(playwright_command, str) else None,
cwd,
)
playwright_manifest_path = (
Path(playwright_manifest).expanduser().resolve()
if isinstance(playwright_manifest, str) and playwright_manifest else None
)
if (protected_playwright is None) != (playwright_manifest_path is None):
raise click.ClickException(
"--playwright-command and --playwright-toolchain-manifest are required together"
)
if protected_playwright is not None:
error = _playwright_command_error(cwd, protected_playwright)
if error is not None:
raise click.ClickException(f"--playwright-command: {error}")
config = RunnerConfig(
jest_command=_protected_command(
jest_command if isinstance(jest_command, str) else None,
Expand All @@ -202,6 +242,8 @@ def _runner_config_from_options(
),
vitest_command=protected_vitest,
vitest_toolchain_manifest=manifest_path,
playwright_command=protected_playwright,
playwright_toolchain_manifest=playwright_manifest_path,
)
if protected_vitest is not None:
try:
Expand All @@ -213,6 +255,26 @@ def _runner_config_from_options(
vitest_command=config.vitest_command,
vitest_toolchain_manifest=config.vitest_toolchain_manifest,
vitest_toolchain_identity=descriptor.identity,
playwright_command=config.playwright_command,
playwright_toolchain_manifest=config.playwright_toolchain_manifest,
adapter_identities=config.adapter_identities,
)
if protected_playwright is not None:
try:
identity = _playwright_toolchain_identity(
cwd, protected_playwright, playwright_manifest_path
)
except (OSError, ValueError) as exc:
raise click.ClickException(f"invalid Playwright toolchain: {exc}") from exc
config = RunnerConfig(
jest_command=config.jest_command,
vitest_command=config.vitest_command,
vitest_toolchain_manifest=config.vitest_toolchain_manifest,
vitest_toolchain_identity=config.vitest_toolchain_identity,
playwright_command=config.playwright_command,
playwright_toolchain_manifest=config.playwright_toolchain_manifest,
playwright_toolchain_identity=identity,
adapter_identities=config.adapter_identities,
)
return config

Expand Down Expand Up @@ -478,6 +540,17 @@ def baseline(ctx: click.Context, module: str, reviewed_by: str, reason: str) ->
type=click.Path(path_type=Path),
help="Protected external Node/Vitest toolchain closure manifest.",
)
@click.option(
"--playwright-command",
envvar="PDD_SYNC_PLAYWRIGHT_COMMAND",
help="Protected absolute external Playwright command argv.",
)
@click.option(
"--playwright-toolchain-manifest",
envvar="PDD_SYNC_PLAYWRIGHT_TOOLCHAIN_MANIFEST",
type=click.Path(path_type=Path),
help="Protected external Playwright toolchain manifest.",
)
@click.pass_context
def validate(
ctx: click.Context,
Expand All @@ -487,6 +560,8 @@ def validate(
jest_command: str | None,
vitest_command: str | None,
vitest_toolchain_manifest: Path | None,
playwright_command: str | None,
playwright_toolchain_manifest: Path | None,
) -> None:
# pylint: disable=too-many-arguments,too-many-positional-arguments
"""Run protected obligations and transactionally finalize trusted evidence."""
Expand All @@ -504,6 +579,9 @@ def validate(
"vitest_command": vitest_command,
"vitest_toolchain_manifest": str(vitest_toolchain_manifest)
if vitest_toolchain_manifest else None,
"playwright_command": playwright_command,
"playwright_toolchain_manifest": str(playwright_toolchain_manifest)
if playwright_toolchain_manifest else None,
},
root,
),
Expand Down
2 changes: 2 additions & 0 deletions pdd/sync_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
)
from .waivers import SyncWaiver, WaiverSet, load_sync_waivers
from .types import (
AssuranceLevel,
ArtifactSnapshot,
BaselineStatus,
CandidateId,
Expand All @@ -137,6 +138,7 @@
)

__all__ = [
"AssuranceLevel",
"ArtifactSnapshot",
"ALIAS_POLICY_PATH",
"AttestationBinding",
Expand Down
10 changes: 10 additions & 0 deletions pdd/sync_core/evidence_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ def attestation_payload(envelope: AttestationEnvelope) -> dict[str, Any]:
payload["binding"]["adapter_identities"] = [
list(item) for item in binding.adapter_identities
]
if binding.playwright_toolchain_identity is not None:
payload["binding"]["playwright_toolchain_identity"] = (
binding.playwright_toolchain_identity
)
return payload


Expand Down Expand Up @@ -127,6 +131,11 @@ def decode_attestation(payload: Mapping[str, Any]) -> AttestationEnvelope:
or len(set(adapter_identities)) != len(adapter_identities)
):
raise TypeError("adapter_identities must be sorted and unique")
toolchain_identity = binding_data.get("playwright_toolchain_identity")
if toolchain_identity is not None and (
not isinstance(toolchain_identity, str) or not toolchain_identity
):
raise TypeError("playwright_toolchain_identity must be a non-empty string")
binding = AttestationBinding(
subject,
_string(binding_data, "snapshot_digest"),
Expand All @@ -136,6 +145,7 @@ def decode_attestation(payload: Mapping[str, Any]) -> AttestationEnvelope:
_string(binding_data, "base_sha"),
_string(binding_data, "checked_sha"),
adapter_identities=adapter_identities,
playwright_toolchain_identity=toolchain_identity,
)
results = tuple(
ObligationEvidence(
Expand Down
6 changes: 6 additions & 0 deletions pdd/sync_core/finalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,14 @@ def _reusable_result(
profile, root=root, ref=head_sha,
config=RunnerConfig(
adapter_identities=envelope.binding.adapter_identities,
playwright_toolchain_identity=envelope.binding.playwright_toolchain_identity,
),
),
TRUSTED_RUNNER_VERSION,
base_sha,
envelope.binding.checked_sha,
adapter_identities=envelope.binding.adapter_identities,
playwright_toolchain_identity=envelope.binding.playwright_toolchain_identity,
)
verifier.verify_current_for_idempotency(envelope, binding, now=now)
ancestry = subprocess.run(
Expand Down Expand Up @@ -325,6 +327,10 @@ def finalize_unit(
if len(matches) != 1:
raise ValueError(f"finalization requires exactly one managed unit: {module}")
profiles = load_verification_profiles(repository_root, manifest)
if profiles.invalid_reasons:
raise ValueError(
"canonical finalization requires valid protected verification profiles"
)
profile = profiles.for_unit(matches[0].unit_id)
if profile is None or not profile.complete:
raise ValueError("finalization requires a complete protected profile")
Expand Down
15 changes: 15 additions & 0 deletions pdd/sync_core/git_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ def read_git_regular_blob(root: Path, ref: str, path: PurePosixPath) -> bytes |
return read_git_blob(root, ref, path)


def read_git_mode(root: Path, ref: str, path: PurePosixPath) -> str | None:
"""Return the exact tree mode for one path without materializing it."""
result = subprocess.run(
["git", "ls-tree", ref, "--", path.as_posix()],
cwd=root,
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0 or not result.stdout.strip():
return None
fields = result.stdout.split(None, 3)
return fields[0] if len(fields) == 4 else None


def resolve_git_commit(root: Path, ref: str) -> str:
"""Resolve one exact commit or fail closed."""
result = subprocess.run(
Expand Down
Loading
Loading