Skip to content

Commit 0bac3a3

Browse files
committed
release: finalize PaperForge v3 runtime
1 parent aecdae9 commit 0bac3a3

56 files changed

Lines changed: 18204 additions & 744 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ jobs:
2121
with:
2222
python-version: ${{ matrix.python }}
2323
cache: pip
24+
- if: runner.os == 'Linux'
25+
run: |
26+
sudo apt-get update
27+
sudo apt-get install -y bubblewrap
28+
sudo chmod u+s "$(command -v bwrap)"
2429
- run: python -m pip install --upgrade pip
2530
- run: python -m pip install -r requirements-dev.txt
2631
- run: python -m pip check
@@ -33,3 +38,28 @@ jobs:
3338
run: python -m build
3439
- if: matrix.python == '3.11'
3540
run: python tools/verify_wheel.py dist/paperforge_research_os-3.0.0-py3-none-any.whl
41+
42+
real-compute-integration:
43+
runs-on: ubuntu-24.04
44+
steps:
45+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
46+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
47+
with:
48+
python-version: "3.11"
49+
cache: pip
50+
- run: python -m pip install -r requirements-dev.txt
51+
- run: sudo tools/setup_linux_compute_gate.sh
52+
- env:
53+
PAPERFORGE_REAL_DOCKER: "1"
54+
PAPERFORGE_REAL_SSH: "1"
55+
PAPERFORGE_REAL_SLURM: "1"
56+
PAPERFORGE_SLURM_IMAGE: /tmp/paperforge-python.sif
57+
PAPERFORGE_REAL_KUBERNETES: "1"
58+
PAPERFORGE_KUBECTL: /usr/local/bin/kubectl
59+
PAPERFORGE_KUBERNETES_CONTEXT: kind-paperforge-v3
60+
PAPERFORGE_KUBERNETES_NAMESPACE: paperforge-v3
61+
run: python -m pytest -q tests/integration/test_real_compute_backends.py
62+
- if: always()
63+
run: |
64+
kind delete cluster --name paperforge-v3 || true
65+
sudo rm -f /tmp/paperforge-python.sif

docs/V3_VALIDATION_CHECKPOINT.md

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,72 +4,102 @@ Date: 2026-07-25
44

55
## Release decision
66

7-
Status: `RELEASE_BLOCKED`
7+
Status: `RELEASE_CANDIDATE`
88

9-
The v3 source is code-complete and the available local end-to-end gates pass, but
10-
the official `v3.0.0` release is intentionally withheld. The approved release
11-
plan requires a real Windows VM run, and no Windows runtime is available on this
12-
host. Consequently there is no `v3.0.0` Git tag, official source ZIP, MambaIR
13-
workspace ZIP, or final delivery report.
9+
The v3 source is code-complete and the macOS, Linux, real-compute, publication,
10+
security, and review gates pass. On 2026-07-26 the release scope was explicitly
11+
changed to defer Windows validation, so the missing Windows runtime no longer
12+
blocks the current macOS/Linux release. The source ZIP, MambaIR workspace ZIP,
13+
delivery report, and `v3.0.0` tag are produced only by the final release step.
1414

1515
## Verified results
1616

1717
- `CODE_VERIFIED`
1818
- macOS 15.7.2 arm64
19-
- Python 3.10.20: 252 tests passed
20-
- Python 3.11.15: 252 tests passed
21-
- Python 3.12.13: 252 tests passed
22-
- Linux x86_64 Python 3.11: 250 non-Git tests passed with the exact
23-
writing dependency set under QEMU; the two real Git tests passed in the
24-
native Linux arm64 VM
25-
- Linux arm64 Python 3.12 clean wheel install: 110 core, security, backend,
26-
publication, and real Git tests passed
19+
- Final macOS full suite: 333 passed, 5 real-compute gates skipped
20+
- Python 3.10.20, 3.11.15, and 3.12.13 passed clean full-suite validation;
21+
the final Docker/GitHub delta passed 12 tests on each version
22+
- Linux arm64 Python 3.12.3 full suite: 325 passed, 6 platform/runtime gates
23+
skipped; the final delta passed 11 tests with one case-insensitive-filesystem
24+
test skipped
2725
- Ruff passed
28-
- Mypy passed for 55 PaperForge source files
26+
- Mypy passed for 64 PaperForge source files
2927
- Node syntax check passed for `frontend/app.js`
30-
- Wheel and sdist built; clean wheel installation, dependency check,
28+
- Wheel built; clean wheel installation, dependency check,
3129
preflight, console entry point, metadata, launchers, templates, few-shot
3230
examples, rubrics, and packaged assets passed
33-
- Final wheel SHA-256:
34-
`3aadce7756b81bcc6fdcad50639589fd957063d8a88900d0f6dc414f51c3e63c`
35-
- Final sdist SHA-256:
36-
`5c82f525f269964b04675e71c6069da36489f97ff97dac0bd2e980e88113ee4a`
37-
- Source secret scan passed: 221 files scanned, zero findings
38-
- Independent final code and Python reviews found no Critical, High, or
39-
Medium findings
31+
- Current verified wheel SHA-256:
32+
`667c5c7cd212590c55fdfcff6ecef2d17578e1622e2f96ffc247a566af931f11`
33+
- Source and reachable Git history secret scan passed: 262 worktree files
34+
and 257 Git blobs scanned, zero findings
35+
- Initial independent review blockers were fixed; final consolidated review
36+
verdict: `APPROVE`, with zero critical, high, medium, or low findings
4037

4138
- `LOCAL_E2E_VERIFIED`
4239
- Local, Docker, OpenSSH, Cloud-SSH, Slurm 23.11.4, and Kind/Kubernetes
4340
backends exercised against real local runtimes
4441
- Submit, status, logs, artifact synchronization, cancellation, and resume
4542
paths exercised
4643
- Frontend loaded through Playwright with no browser console errors
47-
- Generic, CVPR, IEEE, and Elsevier publication profiles compiled and rendered
48-
- MambaIR-GPPNN writing-only candidate: 94/94 claims mapped, five PDF pages
49-
rendered and inspected, protected hashes unchanged, and release manifest
50-
verified
44+
- Generic, CVPR, IEEE, and Elsevier publication profiles compiled, rendered,
45+
and passed authoritative release revalidation in both empty-Bib and
46+
existing-Bib states (eight combinations)
47+
- CVPR author-kit was validated at locked commit
48+
`291758547e923160eb4d37079b7b9f0dfce82355`, tree
49+
`bada7af3a66da84fd610948fd72ce5dd01fb3cc2`, and git-archive SHA-256
50+
`72df21fe120ab08c59980bc9461c6cafc427149e6400684749e731086efce5d6`;
51+
the kit is not redistributed
52+
- Release verification now extracts the locked source bundle into a system
53+
temporary directory, requires byte-identical internal/external source
54+
locks, recompiles, rerenders, recomputes Claim/source invariants, and
55+
requires rebuilt page hashes to match both the official PDF render and the
56+
inspected page hashes
57+
- Every validation render passed decoded-pixel blank-page, saturation, and
58+
border-cropping checks in addition to TeX layout diagnostics
59+
- MambaIR-GPPNN writing-only candidate: 93/93 public claims mapped, five PDF
60+
pages freshly rebuilt, rendered, and inspected; protected hashes, source
61+
locks, bundle checksums, official page hashes, and release manifest passed
5162
- MambaIR-GPPNN source pinned to commit
5263
`5e24e22c0f726fa73fa924afb1d1d186ca677b7b`
64+
- MambaIR-GPPNN source tree SHA-256:
65+
`6129f9393b89bfc91bd29acd978fedae9dd3c987128ff3b9ae802b9fb4c61181`
5366

5467
- `EXTERNAL_SERVICE_AUTH_BLOCKED`
5568
- The one permitted Bailu preflight returned HTTP 401.
5669
- The runtime entered `AUTH_BLOCKED`; no long-generation retry was issued.
5770

5871
- Original repository protection
59-
- `/Users/qjhwc/Desktop/PaperForge/` pre/post archive SHA-256:
72+
- `/Users/qjhwc/Desktop/PaperForge/` pre-upgrade archive SHA-256:
6073
`16524ab87999a1b442d37cda4dfd047fa0f33e3e63a55fdad2b49e64e3762634`
61-
- The original directory remained unchanged.
74+
- All 7,681 files captured by the pre-upgrade content manifest still match
75+
their SHA-256 values; zero files have modification times after the
76+
baseline.
77+
- A prior read-only Git validation changed only the nested `.git` directory
78+
mtime, so a byte-identical tar stream cannot be claimed. No original file
79+
content was modified.
6280

63-
## Open release gate
81+
## Deferred Windows validation
6482

65-
`Windows fresh-install and E2E`: not executed. QEMU, VirtualBox, Parallels,
83+
`Windows fresh-install and E2E` has not been executed. QEMU, VirtualBox, Parallels,
6684
UTM, Wine, and Windows PowerShell are unavailable on this host.
6785

6886
The Windows ACL implementation is fail-closed and has platform-independent
6987
payload tests for trusted owners, untrusted readers/writers, missing DACLs, and
7088
NULL DACLs. These tests do not replace the required execution on real Windows.
7189

72-
This is a verification-environment blocker, not permission to downgrade the
73-
gate. Official release artifacts must remain absent until the Windows run
74-
passes. GitHub publication also remains local-only because no explicit remote
75-
and approval were provided.
90+
Run the complete gate from an elevated or standard PowerShell session with
91+
Python 3.10-3.12, Git, Node, TeX Live/MiKTeX, and Poppler already available:
92+
93+
```powershell
94+
powershell -ExecutionPolicy Bypass -File tools/run_windows_gate.ps1 `
95+
-OutputFile "$env:TEMP\paperforge-windows-gate.json"
96+
```
97+
98+
The script builds one wheel, installs that wheel into fresh Python 3.10, 3.11,
99+
and 3.12 environments, runs isolated import and dependency checks, then runs
100+
the full test suite, Ruff, Mypy, Node syntax validation, the pinned CVPR lock,
101+
and all eight publication profile/Bib combinations. It fails unless the host
102+
is real Windows.
103+
104+
This remains the required gate before claiming Windows support. It is not part
105+
of the current macOS/Linux release scope and must not be reported as passed.

engine/secret_redaction.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,31 @@ def redact_structure(
276276
if isinstance(value, os.PathLike):
277277
return redact_secrets(os.fspath(value), secret_values=values)
278278
return value
279+
280+
281+
def contains_secret(value: Any) -> bool:
282+
"""Return True when a JSON-like value would require secret redaction."""
283+
284+
if isinstance(value, Mapping):
285+
for key, item in value.items():
286+
if _is_secret_field(key) and item not in (None, ""):
287+
return True
288+
normalized_key = _normalize_field_name(key)
289+
if normalized_key in {"command", "argv", "args"} and isinstance(
290+
item, str | Sequence
291+
):
292+
original = [item] if isinstance(item, str) else [str(part) for part in item]
293+
if redact_command(item) != original:
294+
return True
295+
if contains_secret(item):
296+
return True
297+
return False
298+
if isinstance(value, list | tuple | set | frozenset):
299+
return any(contains_secret(item) for item in value)
300+
if isinstance(value, bytes):
301+
rendered = value.decode("utf-8", errors="replace")
302+
return redact_secrets(rendered) != rendered
303+
if isinstance(value, str | os.PathLike):
304+
rendered = os.fspath(value)
305+
return redact_secrets(rendered) != rendered
306+
return False

paperforge/agents.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from pathlib import Path
1111
from typing import Any, Protocol, cast, runtime_checkable
1212

13+
from engine.secret_redaction import redact_structure
14+
1315
from .artifacts import ArtifactStore, sha256_file
1416
from .models import utc_now
1517

@@ -49,6 +51,7 @@ class AgentResultStatus(str, Enum):
4951
ACCEPTED = "ACCEPTED"
5052
COMPLETED = "COMPLETED"
5153
SUCCESS = "COMPLETED"
54+
SKIPPED = "SKIPPED"
5255
BLOCKED = "BLOCKED"
5356
FAILED = "FAILED"
5457

@@ -161,12 +164,14 @@ def persist(
161164
normalized_name = self._normalize_name(name)
162165
created_at = utc_now()
163166
trace_id = f"trace_{uuid.uuid4().hex}"
167+
safe_payload = redact_structure(payload)
168+
safe_metadata = redact_structure(dict(metadata or {}))
164169
envelope: dict[str, Any] = {
165170
"schema_version": TRACE_SCHEMA_VERSION,
166171
"trace_id": trace_id,
167172
"created_at": created_at,
168-
"payload": payload,
169-
"metadata": dict(metadata or {}),
173+
"payload": safe_payload,
174+
"metadata": safe_metadata,
170175
}
171176
canonical = json.dumps(
172177
envelope,
@@ -180,15 +185,15 @@ def persist(
180185
relative_path,
181186
envelope,
182187
kind="trace",
183-
metadata=metadata,
188+
metadata=safe_metadata,
184189
)
185190
return TraceInput(
186191
trace_id=trace_id,
187192
path=record.path,
188193
sha256=record.sha256,
189194
size_bytes=record.size_bytes,
190195
created_at=created_at,
191-
metadata=dict(metadata or {}),
196+
metadata=safe_metadata,
192197
)
193198

194199
def verify(self, trace: TraceInput) -> bool:
@@ -345,6 +350,19 @@ def completed(
345350
trace_outputs=tuple(trace_outputs),
346351
)
347352

353+
@classmethod
354+
def skipped(
355+
cls,
356+
request: AgentRequest,
357+
output: Mapping[str, Any] | None = None,
358+
) -> AgentResult:
359+
return cls(
360+
request_id=request.request_id,
361+
role=request.role,
362+
status=AgentResultStatus.SKIPPED,
363+
output=dict(output or {}),
364+
)
365+
348366
@classmethod
349367
def failed(cls, request: AgentRequest, error: str) -> AgentResult:
350368
return cls(

0 commit comments

Comments
 (0)