Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cc8ad6b
docs(session-09): record stable merge and dev backport prerequisite
mberrys Sep 6, 2026
a505a80
docs(session-09): link dev backport PR #535
mberrys Sep 6, 2026
20f73c3
feat(session-13): add final-artifact SBOM, notices, and relink scaffo…
mberrys Sep 6, 2026
1f69bdf
docs(phase5): backport Session 09 ledger closeout to dev (#535)
mberrys Sep 6, 2026
e40f175
feat(session-13): final-artifact SBOM, notices, and relink scaffoldin…
mberrys Sep 6, 2026
ebde866
feat(session-13): land package-licensing scaffolding on dev (#539)
mberrys Sep 6, 2026
3716765
docs(qualification): record dev baseline after Session 09/13 merges
mberrys Sep 6, 2026
6a55130
docs(qualification): record dev baseline SHAs (#540)
mberrys Sep 7, 2026
1d95568
feat(session-10): close trust-contract repair and async semantics sca…
mberrys Sep 7, 2026
cd30bb7
feat(session-11): freeze fail-closed resource-envelope evidence scaff…
mberrys Sep 7, 2026
eba0404
feat(session-12): lifecycle corpus, shrink, and cross-platform proof …
mberrys Sep 7, 2026
ea676f8
docs(plan): consolidate 0.5.0-0.10.0 train to 0.5.0-0.8.0 (2026-09-06…
mberrys Sep 7, 2026
034e5ea
Merge branch 'dev' into cursor/session-10-trust-qualification
mberrys Sep 7, 2026
2449477
Merge branch 'dev' into cursor/session-11-resource-envelope
mberrys Sep 7, 2026
3876e86
Merge branch 'dev' into cursor/session-12-lifecycle-qualification
mberrys Sep 7, 2026
46ccd49
ci: stop MSI and AppImage packaging on every dev PR
cursoragent Sep 7, 2026
b730e12
feat(session-10): trust-contract repair and async semantics (#541)
mberrys Sep 7, 2026
ee6ac8a
Merge branch 'dev' into cursor/session-11-resource-envelope
mberrys Sep 7, 2026
967d6e8
fix(ci): make resource-envelope entrypoints importable in file mode
mberrys Sep 7, 2026
6a8155f
Merge branch 'dev' into cursor/session-12-lifecycle-qualification
mberrys Sep 7, 2026
5d34c61
fix(ci): route lifecycle test through sanctioned identity API and age…
mberrys Sep 7, 2026
dca4406
fix(core): include QXmlStreamWriter definition and reformat pdfdiff
mberrys Sep 7, 2026
dc5ae08
feat(session-11): resource envelope qualification scaffolding (#542)
mberrys Sep 7, 2026
c7e424c
Merge branch 'dev' into cursor/session-12-lifecycle-qualification
mberrys Sep 7, 2026
d022690
fix(core): include QCryptographicHash definition in pdfdiff
mberrys Sep 7, 2026
11e8f7a
fix(tests): align lifecycle corpus test with manifest schema and inje…
mberrys Sep 7, 2026
67d78ce
feat(session-12): lifecycle corpus and shrink scaffolding (#543)
mberrys Sep 7, 2026
e9d14d1
fix(ci): qualify packaged Qt before MSI cleanup
mberrys Sep 9, 2026
0fb6a78
Fix paired MSI and AppImage qualification (#550)
mberrys Sep 9, 2026
6c89997
test(ci): exercise Linux AppImage relink path
mberrys Sep 9, 2026
07f4197
fix(ci): align workflow contract test with QtRelinkTranscript on dev
cursoragent Sep 9, 2026
41374bd
0.2.0.1 Promotion (#545)
mberrys Sep 10, 2026
ffa5fab
fix(ci): merge stable into unstable and keep relink plus blacksmith c…
cursoragent Sep 10, 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
30 changes: 12 additions & 18 deletions .github/workflows/CreateReleaseDraft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,25 +75,23 @@ jobs:
env:
GH_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}

# Both installer workflows are workflow_dispatch-only. The commit filter and
# head-SHA check keep unrelated artifacts out of the release tag.
# A workflow_dispatch run's headSha identifies the ref used to load the
# workflow, not the source_sha input checked out by the packaging job. Select
# by the immutable SHA embedded in run-name; the downloaded evidence below is
# the authoritative cross-platform provenance check.
- name: Get latest run ID for Linux_AppImage
id: get_linux_run_id
run: |
set -euo pipefail
latest_run=$(gh run list --workflow=Linux_AppImage \
--commit "$EXPECTED_SOURCE_SHA" --status success \
--json databaseId,headSha --jq '.[0] // empty')
--event workflow_dispatch --status success --limit 100 \
--json databaseId,displayTitle \
| jq -c --arg title "Linux_AppImage (${EXPECTED_SOURCE_SHA})" \
'map(select(.displayTitle == $title))[0] // empty')
if [ -z "$latest_run" ]; then
echo "::error::No successful Linux_AppImage run found for source SHA ${EXPECTED_SOURCE_SHA}."
exit 1
fi
run_sha=$(echo "$latest_run" | jq -r .headSha)
if [ "${run_sha,,}" != "${EXPECTED_SOURCE_SHA,,}" ]; then
echo "::error::Linux_AppImage artifacts were built from ${run_sha}, but source_sha is ${EXPECTED_SOURCE_SHA}."
echo "::error::Re-run Linux_AppImage on this exact source commit before creating the release draft."
exit 1
fi
echo "linux_run_id=$(echo "$latest_run" | jq -r .databaseId)" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
Expand All @@ -104,18 +102,14 @@ jobs:
run: |
set -euo pipefail
latest_run=$(gh run list --workflow=Windows_MSI \
--commit "$EXPECTED_SOURCE_SHA" --status success \
--json databaseId,headSha --jq '.[0] // empty')
--event workflow_dispatch --status success --limit 100 \
--json databaseId,displayTitle \
| jq -c --arg title "Windows_MSI (${EXPECTED_SOURCE_SHA})" \
'map(select(.displayTitle == $title))[0] // empty')
if [ -z "$latest_run" ]; then
echo "::error::No successful Windows_MSI run found for source SHA ${EXPECTED_SOURCE_SHA}."
exit 1
fi
run_sha=$(echo "$latest_run" | jq -r .headSha)
if [ "${run_sha,,}" != "${EXPECTED_SOURCE_SHA,,}" ]; then
echo "::error::Windows_MSI artifacts were built from ${run_sha}, but source_sha is ${EXPECTED_SOURCE_SHA}."
echo "::error::Re-run Windows_MSI on this exact source commit before creating the release draft."
exit 1
fi
echo "windows_run_id=$(echo "$latest_run" | jq -r .databaseId)" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/LinuxInstall.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Linux_AppImage
run-name: Linux_AppImage (${{ inputs.source_sha }})

on:
# Dispatch-only. PRs targeting `dev` run ci.yml; MSI/AppImage qualification
Expand Down Expand Up @@ -301,6 +302,27 @@ jobs:
--output "$evidence_dir/evidence.json" \
--report "$evidence_dir/inspection.txt" 2>&1 | tee "$evidence_dir/inspector.txt"

- name: Generate final-artifact SBOM and notices
working-directory: loop
run: |
evidence_dir="$RUNNER_TEMP/loop-package-boundary-linux"
python3 scripts/ci/generate_package_sbom.py \
--evidence "$evidence_dir/evidence.json" \
--output "$evidence_dir/components.spdx.json"
python3 scripts/ci/generate_package_third_party_notices.py \
--evidence "$evidence_dir/evidence.json" \
--output "$evidence_dir/THIRD_PARTY_NOTICES.txt"

- name: Run Qt LGPL relink test
working-directory: loop
env:
QT_QPA_PLATFORM: offscreen
run: |
evidence_dir="$RUNNER_TEMP/loop-package-boundary-linux"
bash scripts/ci/run_qt_relink_test.sh \
"build/${{ env.appimagefilename }}" \
--output "$evidence_dir/qt-relink.txt"

- name: Upload Linux package boundary evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
Expand Down
20 changes: 18 additions & 2 deletions .github/workflows/WindowsInstall.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Windows_MSI
run-name: Windows_MSI (${{ inputs.source_sha }})

on:
# Dispatch-only. PRs targeting `dev` run ci.yml; MSI/AppImage qualification
Expand Down Expand Up @@ -449,20 +450,35 @@ jobs:
$inspectorOutput | Tee-Object -FilePath (Join-Path $evidenceDir "inspector.txt")
if ($LASTEXITCODE -ne 0) { throw "MSI package boundary inspection failed." }

- name: Generate final-artifact SBOM and notices
shell: pwsh
run: |
$evidenceDir = Join-Path $env:RUNNER_TEMP "loop-package-boundary-windows"
python ".\loop\scripts\ci\generate_package_sbom.py" `
--evidence (Join-Path $evidenceDir "evidence.json") `
--output (Join-Path $evidenceDir "components.spdx.json")
python ".\loop\scripts\ci\generate_package_third_party_notices.py" `
--evidence (Join-Path $evidenceDir "evidence.json") `
--output (Join-Path $evidenceDir "THIRD_PARTY_NOTICES.txt")
if ($LASTEXITCODE -ne 0) { throw "Final-artifact notices generation failed." }

- name: Run MSI lifecycle smoke test
shell: pwsh
run: |
# The release MSI is x64 and must install beneath 64-bit Program Files.
# The full editor operator launch is skipped on the hosted runner; the
# packaged native/software Quick startup is exercised by the smoke script.
$evidenceDir = Join-Path $env:RUNNER_TEMP "loop-package-boundary-windows"
New-Item -ItemType Directory -Force -Path $evidenceDir | Out-Null
$msiPath = Join-Path $env:GITHUB_WORKSPACE "loop\build\install\${{ env.msipackagefilename }}"
$installDir = Join-Path ([Environment]::GetFolderPath("ProgramFiles")) "LOOP"
$evidenceDir = Join-Path $env:RUNNER_TEMP "loop-package-boundary-windows"
& "${env:GITHUB_WORKSPACE}\loop\scripts\Invoke-MsiSmokeTest.ps1" `
-MsiPath $msiPath `
-InstallDir $installDir `
-SourceSha $env:LOOP_SOURCE_SHA `
-SkipEditorLaunch

-SkipEditorLaunch `
-QtRelinkTranscript (Join-Path $evidenceDir "qt-relink.txt")

- name: Upload Windows package boundary evidence
if: always()
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ permissions:
contents: read

jobs:
package_script_tests:
strategy:
matrix:
os: [ubuntu-22.04, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Test package lifecycle scripts with fake packages
run: python -m unittest scripts.ci.test_run_qt_relink_test -v
- name: Test Linux AppImage Qt relink script with fake AppImage
if: runner.os == 'Linux'
run: python -m unittest scripts.ci.test_run_qt_relink_linux -v

source_integrity:
runs-on: ubuntu-24.04
steps:
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/reusable-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ jobs:
- name: Verify version policy
working-directory: loop
run: python3 scripts/ci/check_version_policy.py
- name: Verify resource-envelope contracts
working-directory: loop
run: |
python3 -m unittest scripts.resource_envelope.test_validate_envelope scripts.resource_envelope.test_pathological_workload scripts.resource_envelope.test_run_matrix scripts.resource_envelope.test_budget_exhaustion_corpus scripts.qualification.test_validate_resource_envelope_evidence -v
python3 scripts/resource_envelope/validate_envelope.py docs/generated/huge-document-envelope.json
python3 scripts/qualification/validate_resource_envelope_evidence.py
python3 scripts/resource_envelope/pathological_workload.py \
--output "${RUNNER_TEMP}/loop-pathological-vector.pdf" \
--page-count 256 --operations 256 --family pathological-vector
- name: Verify processing-budget exhaustion corpus
working-directory: loop
run: python3 scripts/budget_exhaustion/generate_corpus.py --check
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/reusable-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ jobs:
working-directory: loop
shell: pwsh
run: |
python -m unittest scripts.resource_envelope.test_validate_envelope scripts.resource_envelope.test_pathological_workload -v
python -m unittest scripts.resource_envelope.test_validate_envelope scripts.resource_envelope.test_pathological_workload scripts.resource_envelope.test_run_matrix scripts.resource_envelope.test_budget_exhaustion_corpus scripts.qualification.test_validate_resource_envelope_evidence -v
python scripts/resource_envelope/validate_envelope.py docs/generated/huge-document-envelope.json
python scripts/qualification/validate_resource_envelope_evidence.py
python scripts/resource_envelope/pathological_workload.py `
--output "$env:RUNNER_TEMP\loop-pathological-vector.pdf" `
--page-count 256 --operations 256 --family pathological-vector
Expand Down
94 changes: 66 additions & 28 deletions LoopLibCore/sources/pdfdiff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@
#include "pdfcms.h"
#include "pdfconstants.h"
#include "pdfalgorithmlcs.h"
#include "pdfjobscheduler.h"
#include "pdfpainter.h"

#include <QtConcurrent/QtConcurrent>
#include <QCryptographicHash>
#include <QUuid>
#include <QXmlStreamWriter>

#include "pdfdbgheap.h"

Expand Down Expand Up @@ -89,7 +92,6 @@ PDFDiff::PDFDiff(QObject* parent) :
m_cancelled(false),
m_textAnalysisAlgorithm(PDFDocumentTextFlowFactory::Algorithm::Layout)
{

}

PDFDiff::~PDFDiff()
Expand Down Expand Up @@ -138,12 +140,38 @@ void PDFDiff::start()

if (m_options.testFlag(Asynchronous))
{
m_futureWatcher = std::nullopt;
m_futureWatcher.emplace();
if (m_jobFinishedConnection)
{
disconnect(m_jobFinishedConnection);
m_jobFinishedConnection = {};
}

pdf::PDFJobSpec spec;
spec.jobId = QStringLiteral("pdf-diff-%1").arg(QUuid::createUuid().toString(QUuid::WithoutBraces));
spec.kind = pdf::PDFJobKind::Batch;
spec.priority = pdf::PDFJobPriority::Background;
spec.operationId = QStringLiteral("pdf.diff");

m_future = QtConcurrent::run(std::bind(&PDFDiff::perform, this));
connect(&*m_futureWatcher, &QFutureWatcher<PDFDiffResult>::finished, this, &PDFDiff::onComparationPerformed);
m_futureWatcher->setFuture(m_future);
m_activeJobId = pdf::PDFJobScheduler::global().submit(spec, [this](pdf::PDFJobContext& context)
{
if (context.isCancellationRequested())
{
m_cancelled = true;
return;
}
m_result = perform(); });

m_jobFinishedConnection = connect(&pdf::PDFJobScheduler::global(),
&pdf::PDFJobScheduler::jobFinished,
this,
[this](const pdf::PDFJobSnapshot& snapshot)
{
if (snapshot.jobId != m_activeJobId)
{
return;
}
onComparationPerformed(snapshot.status == pdf::PDFJobStatus::Cancelled);
});
}
else
{
Expand All @@ -155,12 +183,20 @@ void PDFDiff::start()

void PDFDiff::stop()
{
if (m_futureWatcher && !m_futureWatcher->isFinished())
if (m_activeJobId.isEmpty())
{
return;
}

const QString jobId = m_activeJobId;
m_cancelled = true;
pdf::PDFJobScheduler::global().cancel(jobId);
pdf::PDFJobScheduler::global().waitForFinished(jobId);
m_activeJobId.clear();
if (m_jobFinishedConnection)
{
// Do stop only if process doesn't finished already.
// If we are finished, we do not want to set cancelled state.
m_cancelled = true;
m_futureWatcher->waitForFinished();
disconnect(m_jobFinishedConnection);
m_jobFinishedConnection = {};
}
}

Expand Down Expand Up @@ -224,7 +260,7 @@ void PDFDiff::stepProgress()
struct PDFDiffPageContext
{
PDFInteger pageIndex = 0;
std::array<uint8_t, 64> pageHash = { };
std::array<uint8_t, 64> pageHash = {};
PDFPrecompiledPage::GraphicPieceInfos graphicPieces;
PDFDocumentTextFlow text;
};
Expand Down Expand Up @@ -342,13 +378,13 @@ void PDFDiff::performSteps(const std::vector<PDFInteger>& leftPages,
std::vector<PDFDiffPageContext> rightPreparedPages;

PDFDiffHelper::PageSequence pageSequence;
std::map<size_t, size_t> pageMatches; // Indices are real page indices, not indices to page contexts
std::map<size_t, size_t> pageMatches; // Indices are real page indices, not indices to page contexts

auto createDiffPageContext = [](auto pageIndex)
{
PDFDiffPageContext context;
context.pageIndex = pageIndex;
return context;
PDFDiffPageContext context;
context.pageIndex = pageIndex;
return context;
};
std::transform(leftPages.cbegin(), leftPages.cend(), std::back_inserter(leftPreparedPages), createDiffPageContext);
std::transform(rightPages.cbegin(), rightPages.cend(), std::back_inserter(rightPreparedPages), createDiffPageContext);
Expand Down Expand Up @@ -696,7 +732,7 @@ void PDFDiff::performCompare(const std::vector<PDFDiffPageContext>& leftPrepared
compareCharacters);
algorithm.perform();
PDFAlgorithmLongestCommonSubsequenceBase::Sequence sequence = algorithm.getSequence();
PDFAlgorithmLongestCommonSubsequenceBase::markSequence(sequence, { }, { });
PDFAlgorithmLongestCommonSubsequenceBase::markSequence(sequence, {}, {});
PDFAlgorithmLongestCommonSubsequenceBase::SequenceItemRanges modifiedRanges = PDFAlgorithmLongestCommonSubsequenceBase::getModifiedRanges(sequence);

// Merge modified sequences separated by just space
Expand Down Expand Up @@ -778,9 +814,9 @@ void PDFDiff::performCompare(const std::vector<PDFDiffPageContext>& leftPrepared
pageIndex1 = textItem->pageIndex;
}

if (static_cast< std::size_t >( textCompareItem.charIndex ) + textCompareItem.charCount <= textItem->characterBoundingRects.size())
if (static_cast<std::size_t>(textCompareItem.charIndex) + textCompareItem.charCount <= textItem->characterBoundingRects.size())
{
const size_t startIndex = textCompareItem.charIndex;
const size_t startIndex = textCompareItem.charIndex;
const size_t endIndex = startIndex + textCompareItem.charCount;

for (size_t i = startIndex; i < endIndex; ++i)
Expand All @@ -806,9 +842,9 @@ void PDFDiff::performCompare(const std::vector<PDFDiffPageContext>& leftPrepared
pageIndex2 = textItem->pageIndex;
}

if (static_cast< std::size_t >(textCompareItem.charIndex) + textCompareItem.charCount <= textItem->characterBoundingRects.size())
if (static_cast<std::size_t>(textCompareItem.charIndex) + textCompareItem.charCount <= textItem->characterBoundingRects.size())
{
const size_t startIndex = textCompareItem.charIndex;
const size_t startIndex = textCompareItem.charIndex;
const size_t endIndex = startIndex + textCompareItem.charCount;

for (size_t i = startIndex; i < endIndex; ++i)
Expand Down Expand Up @@ -900,10 +936,15 @@ void PDFDiff::finalizeGraphicsPieces(PDFDiffPageContext& context)
std::copy(hash.data(), hash.data() + size, context.pageHash.data());
}

void PDFDiff::onComparationPerformed()
void PDFDiff::onComparationPerformed(bool cancelled)
{
m_cancelled = false;
m_result = m_future.result();
m_cancelled = cancelled;
m_activeJobId.clear();
if (m_jobFinishedConnection)
{
disconnect(m_jobFinishedConnection);
m_jobFinishedConnection = {};
}
Q_EMIT comparationFinished();
}

Expand Down Expand Up @@ -933,7 +974,6 @@ void PDFDiff::setTextAnalysisAlgorithm(PDFDocumentTextFlowFactory::Algorithm tex
PDFDiffResult::PDFDiffResult() :
m_result(true)
{

}

void PDFDiffResult::addPageMoved(PDFInteger pageIndex1, PDFInteger pageIndex2)
Expand Down Expand Up @@ -1799,12 +1839,10 @@ PDFDiffResultNavigator::PDFDiffResultNavigator(QObject* parent) :
m_diffResult(nullptr),
m_currentIndex(0)
{

}

PDFDiffResultNavigator::~PDFDiffResultNavigator()
{

}

void PDFDiffResultNavigator::setResult(const PDFDiffResult* diffResult)
Expand Down
Loading
Loading