Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ Supported notarization inputs:
- The supported arm64 release-candidate path includes its application runtime, core Python pack, and FFmpeg/FFprobe; creators do not install Python, pip, FFmpeg, or a virtual environment for that path.
- Baseline transcription is included and the verified Whisper base model downloads into app-managed storage on first transcription. Once installed, baseline transcription can run without model-network access.
- Optional capabilities may not be included. Do not claim all AI is offline or that all capabilities are bundled.
- Candidate metadata records that transitive runtime wheel hashes are not fully locked; 3B.5A does not claim fully reproducible builds.
- The macOS arm64 runtime package inputs are version- and SHA-256-locked. Source-built Whisper uses the pinned portable Python toolchain without isolated build downloads. Generated wheels and the final DMG are still not claimed byte-for-byte reproducible.
- `npm run release:ffmpeg` verifies that FFmpeg/FFprobe execute from the release bundle and packages non-system macOS dylibs. Do not manually copy host FFmpeg executables into a release.
- The bundle manifest records whether the selected FFmpeg supports ASS burn-in captions. Releases without that filter use the tested sidecar `.srt` fallback and must state that in their notes.
- Parakeet TDT v3 requires optional NVIDIA NeMo ASR dependencies.
Expand Down
39 changes: 39 additions & 0 deletions runtime/core-darwin-arm64-py311.lock.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Hash-locked runtime artifacts for native macOS arm64 / Python 3.11.15.
# Generated from the reviewed constraints graph. openai-whisper is intentionally
# locked to its source archive and built with the hash-pinned portable Python
# toolchain using --no-build-isolation.
annotated-doc==0.0.5 --hash=sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101
annotated-types==0.8.0 --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0
anyio==4.15.1 --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101
certifi==2026.7.22 --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775
charset-normalizer==3.5.2 --hash=sha256:3d21b8b13c7592db2ac5e544a6d83187b995257472b0c9e8351b6d507ae37ed6
click==8.5.0 --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360
fastapi==0.138.1 --hash=sha256:b994cae7ba8b82c976a728b544244de31333fa5f7d261f9a1dffe526444cae23
filelock==4.1.1 --hash=sha256:3f4a557945a7b0f95efeb1f432267affe5d45ac8ddde2aed1b97ebb62382c089
fsspec==2026.9.0 --hash=sha256:8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f
h11==0.16.0 --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
idna==3.20 --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
jinja2==3.1.6 --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
llvmlite==0.50.0 --hash=sha256:818b3d4845ac8e126e23cb500867570d0602a42a43e67b14acec31f046e03130
markupsafe==3.0.4 --hash=sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91
more-itertools==11.2.0 --hash=sha256:e4c522352a24664c974d0be8382944107ae638281a06391a5a538366b5707fe5
mpmath==1.3.0 --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
networkx==3.6.1 --hash=sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762
numba==0.68.0 --hash=sha256:50399af9d3799a4677044294861169c614bd7e1d8bbfc9479f78a67ab28ff427
numpy==1.26.4 --hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef
openai-whisper==20250625 --hash=sha256:37a91a3921809d9f44748ffc73c0a55c9f366c85a3ef5c2ae0cc09540432eb96
pydantic==2.13.4 --hash=sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba
pydantic-core==2.46.4 --hash=sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c
python-multipart==0.0.32 --hash=sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23
regex==2026.9.29 --hash=sha256:066d0e3dbfdd739bce2bf8c2a41dd16f73e3d8adc2eb06dd803a36a307f56075
requests==2.34.2 --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0
setuptools==81.0.0 --hash=sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6
starlette==1.8.0 --hash=sha256:dfdd6b29c26483288088d990eee59631dedadd66ce20d203402a7ca8e3c4656f
sympy==1.14.0 --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5
tiktoken==0.14.0 --hash=sha256:cd8ca1305c1c902fe42c486165f2e4808d9997625c98ffb05b9e0366d99d3948
torch==2.12.1 --hash=sha256:ef81f503912effea2ce3d9b12a2e3a6ed488943e91271c90c7a829f60baf6aa2
tqdm==4.70.1 --hash=sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73
typing-extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
typing-inspection==0.4.4 --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147
urllib3==2.8.0 --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3
uvicorn==0.49.0 --hash=sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f
19 changes: 19 additions & 0 deletions scripts/check-packaged-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ function inspectPackage(appPath) {
}
if (!fs.existsSync(constraintsPath)) fail(`runtime constraints source is missing: ${constraints.source}`);
if (sha256(constraintsPath) !== constraints.sha256) fail('runtime constraints SHA-256 does not match the committed source');
const dependencyLock = manifest.packs?.lock;
if (
!dependencyLock
|| typeof dependencyLock.source !== 'string'
|| !/^[a-f0-9]{64}$/.test(dependencyLock.sha256 || '')
|| dependencyLock.requireHashes !== true
|| dependencyLock.buildIsolation !== false
) {
fail('runtime manifest is missing valid hash-locked dependency provenance');
}
if (path.isAbsolute(dependencyLock.source)) fail('runtime dependency lock source must be repository-relative');
const dependencyLockPath = path.resolve(root, dependencyLock.source);
const relativeDependencyLockPath = path.relative(root, dependencyLockPath);
if (!relativeDependencyLockPath || relativeDependencyLockPath.startsWith('..') || path.isAbsolute(relativeDependencyLockPath)) {
fail('runtime dependency lock source escapes the repository root');
}
if (!fs.existsSync(dependencyLockPath)) fail(`runtime dependency lock source is missing: ${dependencyLock.source}`);
if (sha256(dependencyLockPath) !== dependencyLock.sha256) fail('runtime dependency lock SHA-256 does not match the committed source');
let modelManifest;
try {
modelManifest = readModelManifest(modelManifestPath);
Expand Down Expand Up @@ -232,6 +250,7 @@ function inspectPackage(appPath) {
console.log(`FFmpeg/bin: ${binSize} bytes`);
console.log(`Manifest: ${normalized.schema}, ${normalized.target.id}, Python ${normalized.python.version}`);
console.log(`Core constraints: ${constraints.source}, SHA-256 ${constraints.sha256}`);
console.log(`Core dependency lock: ${dependencyLock.source}, SHA-256 ${dependencyLock.sha256}, requireHashes=true, buildIsolation=false`);
console.log(`Model manifest: ${modelManifest.id}, ${modelManifest.revision}, ${modelManifest.expectedBytes} bytes`);
console.log('Core imports: fastapi, uvicorn, pydantic, requests, torch, whisper==20250625');
console.log('Optional packages bundled: No');
Expand Down
7 changes: 7 additions & 0 deletions scripts/check-public-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ function validateManifest(manifest, { allowPendingAttestation = false } = {}) {
assert(manifest.codeSignature?.hardenedRuntime === false, 'public ad-hoc candidate must not claim Hardened Runtime');
assert(manifest.runtime?.mode === 'packaged-bundled' && manifest.runtime?.pythonSource === 'bundled', 'runtime must be packaged-bundled');
assert(manifest.runtime?.target?.platform === 'darwin' && manifest.runtime?.target?.arch === 'arm64', 'runtime target must be darwin arm64');
assert(typeof manifest.runtime?.dependencyLock?.source === 'string' && manifest.runtime.dependencyLock.source.length > 0, 'runtime dependency lock source is missing');
assert(!path.isAbsolute(manifest.runtime.dependencyLock.source), 'runtime dependency lock source must be repository-relative');
assert(/^[0-9a-f]{64}$/.test(manifest.runtime.dependencyLock.sha256 || ''), 'runtime dependency lock SHA-256 is missing');
assert(manifest.runtime.dependencyLock.requireHashes === true, 'runtime dependency lock must require hashes');
assert(manifest.runtime.dependencyLock.buildIsolation === false, 'runtime dependency lock must disable isolated source builds');
assert(manifest.reproducible === false, 'public alpha must not claim byte-for-byte reproducibility');
assert(/hash-locked/i.test(manifest.reproducibilityNote || '') && /not claimed byte-for-byte reproducible/i.test(manifest.reproducibilityNote || ''), 'runtime reproducibility note is inaccurate');
assert(manifest.model?.embedded === false, 'model weights must not be embedded');
if (manifest.bundleSize) {
assert(manifest.bundleSize.schema === 'scriptcut.bundle-size.v1', 'bundle-size summary schema is invalid');
Expand Down
4 changes: 3 additions & 1 deletion scripts/prepare-public-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function publicManifest({ pkg, tag, commit, artifact, candidate, dmgAttestation
pythonVersion: candidate.runtime.pythonVersion,
pythonBuild: candidate.runtime.pythonBuild,
manifestSha256: candidate.runtime.manifestSha256,
dependencyLock: candidate.runtime.dependencyLock,
},
coreInventorySha256: candidate.coreInventorySha256,
ffmpeg: candidate.ffmpeg,
Expand All @@ -141,7 +142,7 @@ function publicManifest({ pkg, tag, commit, artifact, candidate, dmgAttestation
dmgAttestation,
},
reproducible: false,
reproducibilityNote: 'Transitive runtime wheel hashes are not fully locked in this phase.',
reproducibilityNote: 'Runtime package source/wheel inputs are hash-locked; generated wheels and the final DMG are not claimed byte-for-byte reproducible.',
};
}

Expand Down Expand Up @@ -187,6 +188,7 @@ macOS Apple Silicon (arm64) only. Intel, Windows, Linux, and browser builds are
## What's included

- A self-contained Electron application with portable Python ${manifest.runtime.pythonVersion} and the pinned core runtime.
- A hash-locked macOS arm64 runtime package set; source-built Whisper uses the pinned portable Python toolchain without isolated build downloads.
- Bundled FFmpeg and FFprobe for local export.
- Baseline Whisper transcription code and a trusted model manifest.
- Optional capabilities may be absent from this build.
Expand Down
13 changes: 11 additions & 2 deletions scripts/prepare-python-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const modelInputPath = path.join(root, 'runtime', 'models', 'whisper-base.json')
const generatedModelManifestPath = path.join(buildRoot, 'manifests', 'model-manifest.json');
const coreInputPath = path.join(root, 'runtime', 'core-darwin-arm64-py311.txt');
const coreConstraintsPath = path.join(root, 'runtime', 'core-darwin-arm64-py311.constraints.txt');
const coreLockPath = path.join(root, 'runtime', 'core-darwin-arm64-py311.lock.txt');
// Torch imports torch.testing through its normal autograd import path.
const RUNTIME_REQUIRED_TEST_DIRECTORIES = new Set(['torch/testing']);

Expand Down Expand Up @@ -211,6 +212,7 @@ function pythonVersion(interpreterPath, environment) {
function installCorePack(interpreterPath) {
if (!fs.existsSync(coreInputPath)) fail(`missing committed core input: ${path.relative(root, coreInputPath)}`);
if (!fs.existsSync(coreConstraintsPath)) fail(`missing committed core constraints: ${path.relative(root, coreConstraintsPath)}`);
if (!fs.existsSync(coreLockPath)) fail(`missing committed core artifact lock: ${path.relative(root, coreLockPath)}`);
fs.rmSync(corePackRoot, { recursive: true, force: true });
fs.mkdirSync(corePackRoot, { recursive: true });
const environment = isolatedEnvironment({
Expand All @@ -222,9 +224,10 @@ function installCorePack(interpreterPath) {
'-m', 'pip', 'install',
'--disable-pip-version-check',
'--no-compile',
'--no-build-isolation',
'--require-hashes',
'--target', corePackRoot,
'--constraint', coreConstraintsPath,
'--requirement', coreInputPath,
'--requirement', coreLockPath,
], { env: environment, inherit: true });
}

Expand Down Expand Up @@ -285,6 +288,12 @@ function writeManifest(relativeInterpreter, version) {
source: path.relative(root, coreConstraintsPath).split(path.sep).join('/'),
sha256: sha256(coreConstraintsPath),
},
lock: {
source: path.relative(root, coreLockPath).split(path.sep).join('/'),
sha256: sha256(coreLockPath),
requireHashes: true,
buildIsolation: false,
},
},
security: { tokenRequired: true },
};
Expand Down
17 changes: 16 additions & 1 deletion scripts/release-alpha.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ function readProvenance() {
}

async function createReleaseManifest(pkg, artifact, checksums, provenance, bundleSize) {
const dependencyLock = provenance.runtime.packs?.lock;
if (
!dependencyLock
|| !/^[a-f0-9]{64}$/.test(dependencyLock.sha256 || '')
|| dependencyLock.requireHashes !== true
|| dependencyLock.buildIsolation !== false
) {
throw new Error('Runtime manifest is missing hash-locked dependency provenance.');
}
const manifest = {
schema: 'scriptcut.release.v1',
productName: pkg.build?.productName || pkg.name,
Expand All @@ -164,6 +173,12 @@ async function createReleaseManifest(pkg, artifact, checksums, provenance, bundl
pythonVersion: provenance.runtime.python.version,
pythonBuild: provenance.runtime.python.build,
manifestSha256: await checksumFile(provenance.runtimeManifestPath),
dependencyLock: {
source: dependencyLock.source,
sha256: dependencyLock.sha256,
requireHashes: true,
buildIsolation: false,
},
},
coreInventorySha256: await checksumFile(provenance.coreInventoryPath),
ffmpeg: {
Expand Down Expand Up @@ -197,7 +212,7 @@ async function createReleaseManifest(pkg, artifact, checksums, provenance, bundl
signed: false,
notarized: false,
reproducible: false,
reproducibilityNote: 'Transitive runtime wheel hashes are not fully locked in this phase.',
reproducibilityNote: 'Runtime package source/wheel inputs are hash-locked; generated wheels and the final DMG are not claimed byte-for-byte reproducible.',
};
const manifestPath = path.join(releaseMetadataDir, 'release-manifest.json');
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
Expand Down
7 changes: 7 additions & 0 deletions scripts/smoke-public-release.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ function writeFixture(rootDir) {
pythonVersion: '3.11.15',
pythonBuild: '20260807',
manifestSha256: '1'.repeat(64),
dependencyLock: {
source: 'runtime/core-darwin-arm64-py311.lock.txt',
sha256: '6'.repeat(64),
requireHashes: true,
buildIsolation: false,
},
},
coreInventorySha256: '2'.repeat(64),
codeSignature: { type: 'ad-hoc', structurallyValid: true, hardenedRuntime: false },
Expand Down Expand Up @@ -86,6 +92,7 @@ async function main() {
const manifest = JSON.parse(fs.readFileSync(result.manifestPath, 'utf8'));
const notes = fs.readFileSync(path.join(fixture.outputDir, 'RELEASE_NOTES.md'), 'utf8');
validateManifest(manifest);
assert(manifest.runtime.dependencyLock.sha256 === '6'.repeat(64), 'runtime dependency lock provenance was not propagated');
assert(manifest.bundleSize?.schema === 'scriptcut.bundle-size.v1', 'bundle-size summary was not propagated to public manifest');
validateNotes(notes);
assert(/--signer-workflow[\s\S]*--source-digest/.test(notes), 'generated RELEASE_NOTES omit constrained attestation verification');
Expand Down
11 changes: 10 additions & 1 deletion scripts/smoke-release-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ function createSourceFixture() {
pythonSource: 'bundled',
target: { platform: 'darwin', arch: 'arm64' },
manifestSha256: '2'.repeat(64),
dependencyLock: {
source: 'runtime/core-darwin-arm64-py311.lock.txt',
sha256: '5'.repeat(64),
requireHashes: true,
buildIsolation: false,
},
},
coreInventorySha256: '3'.repeat(64),
ffmpeg: { manifestSha256: '4'.repeat(64) },
Expand Down Expand Up @@ -123,10 +129,13 @@ async function validateReleaseDirectory(releaseDir) {
assert(manifest.runtime?.mode === 'packaged-bundled', 'runtime mode must be packaged-bundled');
assert(manifest.runtime?.pythonSource === 'bundled', 'Python source must be bundled');
assert(manifest.runtime?.target?.platform === 'darwin' && manifest.runtime?.target?.arch === 'arm64', 'runtime target must be darwin-arm64');
assert(manifest.runtime?.dependencyLock?.source === 'runtime/core-darwin-arm64-py311.lock.txt', 'runtime dependency lock source is missing');
assert(/^[0-9a-f]{64}$/.test(manifest.runtime?.dependencyLock?.sha256 || ''), 'runtime dependency lock SHA-256 is missing');
assert(manifest.runtime?.dependencyLock?.requireHashes === true && manifest.runtime?.dependencyLock?.buildIsolation === false, 'runtime dependency lock policy is missing');
assert(manifest.artifact?.filename && path.basename(manifest.artifact.filename) === manifest.artifact.filename, 'artifact filename must be relative');
assert(Number.isInteger(manifest.artifact.bytes) && manifest.artifact.bytes > 0, 'artifact bytes must be present');
assert(/^[0-9a-f]{64}$/.test(manifest.artifact.sha256), 'artifact SHA-256 must be present');
for (const field of [manifest.runtime.manifestSha256, manifest.coreInventorySha256, manifest.ffmpeg.manifestSha256, manifest.model.manifestSha256]) {
for (const field of [manifest.runtime.manifestSha256, manifest.runtime.dependencyLock.sha256, manifest.coreInventorySha256, manifest.ffmpeg.manifestSha256, manifest.model.manifestSha256]) {
assert(/^[0-9a-f]{64}$/.test(field), 'provenance SHA-256 must be present');
}
walkValues(manifest, (value) => {
Expand Down
6 changes: 6 additions & 0 deletions scripts/smoke-release-notes.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ function writeCandidate(directory) {
pythonVersion: '3.11.15',
pythonBuild: '20260807',
manifestSha256: '1'.repeat(64),
dependencyLock: {
source: 'runtime/core-darwin-arm64-py311.lock.txt',
sha256: '6'.repeat(64),
requireHashes: true,
buildIsolation: false,
},
},
coreInventorySha256: '2'.repeat(64),
codeSignature: { type: 'ad-hoc', structurallyValid: true, hardenedRuntime: false },
Expand Down
Loading
Loading