From d492d37b711d0767db8179d84a062d6a6107c990 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 18 Oct 2026 04:15:29 +0200 Subject: [PATCH 1/2] Bind installed evidence to the audited current qualifier version --- docs/roadmaps/wasm-shared-01.md | 50 ++++- .../wasm-release/bind-installed-release.py | 4 +- scripts/wasm-release/installed-evidence.py | 31 ++- tests/release/wasm/test-installed-evidence.py | 198 ++++++++++++++++-- 4 files changed, 244 insertions(+), 39 deletions(-) diff --git a/docs/roadmaps/wasm-shared-01.md b/docs/roadmaps/wasm-shared-01.md index e9372428..a5ed3d80 100644 --- a/docs/roadmaps/wasm-shared-01.md +++ b/docs/roadmaps/wasm-shared-01.md @@ -57,15 +57,47 @@ and original promotion-binding SHA-256 is `fa7a48d1b389116b18f00b5ec94dd83e82bfd3fd9e4032fe052eb13eab019119`. The source hold ended after accepted original terminal/readback evidence. -The finite read-only installed binder now selects only this published 0.6 P/tree. -The selected qualifier Q must be the actual clean current main/dispatch executor, -with the complete closed P-to-Q path audit and genuine original publication -receipts. Runtime, build, workflow and profile bytes remain unchanged. Historical -0.5/0.4 installed successes stay immutable evidence, not alternative current -execution routes. Qualifier source CI/delivery and a separately selected fresh -org/public installed operation remain required. Last accepted installed remains -0.5 at Q `c938e9158542e3a0572b62dd09c01ad01cbb344f`; publication alone accepts -no installed 0.6, SC2 adoption, image/native workload or product outcome. +The finite read-only installed binder selects only this published 0.6 P/tree. +[PR724](https://github.com/FS-GG/FS.GG.Game/pull/724) delivered qualifier +`025cbfd4b3efed0a3baf87c52ebae404124cd814`, with exact tested/delivered tree +`4fc6eb4b9d745d1f0869fa32a411ea00a7f09448`. Its original source qualification +39086999965/attempt1 passed50 Lifecycle controls,36 browser cases and the final +default53/selected16 compiled cohorts. That source result did not exercise the +feed-only evidence projection. + +The first selected served-feed operation, +[39088631914/attempt1](https://github.com/FS-GG/FS.GG.Game/actions/runs/39088631914), +failed on both feeds. Each actual consumer compiled and passed36 browser cases; +the consumer's recorded runtime was0, collection1 and direct cleanup0, with +descendant settlement unproved. The first recorded projection refusal concerns +the actual Fable library `Array.js`: the collector requested an obsolete +`fs.gg.wasm.browser.0.5.0.nupkg` original. The same duplicated version assumption +also affected package-cache, Fable package-module, SDK and assembled receipt +paths. The existing shell trap preserved the runtime/collection distinction and +exited1 on collection failure; the selected-supervisor stage was not reached. +Both ineligible original artifacts and native failures remain retained. Neither +the successful consumer runtime nor the later failed collector is installed +acceptance, and no unchanged retry or package replacement is selected. + +The prepared collector correction derives one current namespace from the audited +tracked `version.props`, with stable-version validation and no environment/CLI +version override or historical fallback. Consumer, selected dependency originals, +Fable roles, SDK, merge and authenticated receipt joins use that same input. +Existing mandatory roles, byte joins, link/census/size refusals and first-cause +ordering remain. Inert controls run the maintained consumer and assemble CLI +actions against a declared synthetic file topology, including current dependency +bytes, stale-cache refusal and a mismatched authenticated SDK receipt. These are +projection controls; they execute no fixture runtime or installed consumer. + +Any successor qualifier must be the actual clean current main/dispatch executor, +with a reviewed finite P-to-Q file/blob audit and genuine original publication +receipts. Collector/test changes require an explicit bounded qualifier closure; +the earlier seven-file binder does not authorize them. Runtime, workflow, package +pins and profile remain unchanged. Historical0.5/0.4 successes remain immutable +evidence, not execution alternatives. Fresh source qualification and a separately +selected installed operation remain required. Last accepted installed remains0.5 +at Q `c938e9158542e3a0572b62dd09c01ad01cbb344f`; installed0.6, image/native and +whole-product acceptance remain open. ### First 0.6 source qualification and exact follower-expiry proof diff --git a/scripts/wasm-release/bind-installed-release.py b/scripts/wasm-release/bind-installed-release.py index 9ad0dff9..4c7e5bf1 100644 --- a/scripts/wasm-release/bind-installed-release.py +++ b/scripts/wasm-release/bind-installed-release.py @@ -19,6 +19,8 @@ # for arbitrary descendants or semantic changes inside a broad scripts prefix. INSTALLED_PATHS = frozenset({ 'scripts/wasm-release/bind-installed-release.py', + 'scripts/wasm-release/installed-evidence.py', + 'tests/release/wasm/test-installed-evidence.py', 'tests/release/wasm/test-installed-org-workflow.py', 'tests/release/wasm/test-release-promotion.py', 'docs/roadmaps/wasm-shared-01.md', @@ -90,7 +92,7 @@ def fingerprint(revision, blob): assert rows, 'installed correction must be an explicit reviewed source change' return {'schema':'fsgg.wasm.installed-inputs/v1','publishedSource':source,'publishedTree':PUBLISHED_TREE, 'qualifierSource':qualifier,'qualifierTree':git('rev-parse',qualifier+'^{tree}'),'changes':rows, - 'consumerInputs':'all paths outside exact reviewed repair closure byte-identical'} + 'consumerInputs':'all paths outside exact reviewed qualifier correction byte-identical to P; published packages/SDK remain P inputs, evidence collector/binder are selected Q inputs'} def bind_executor(source, executor, version): diff --git a/scripts/wasm-release/installed-evidence.py b/scripts/wasm-release/installed-evidence.py index 8b0dad4d..264cd82f 100644 --- a/scripts/wasm-release/installed-evidence.py +++ b/scripts/wasm-release/installed-evidence.py @@ -6,11 +6,22 @@ import json import os import pathlib +import re import stat import tarfile +import xml.etree.ElementTree as ET import zipfile ROOT = pathlib.Path(__file__).resolve().parents[2] +# The qualifier's audited source fixes this input; callers cannot select an old +# cache namespace through an environment variable or a second version literal. +_versions = ET.parse(ROOT/'eng/wasm-shared/version.props').findall('.//WasmSharedVersion') +if len(_versions) != 1: + raise ValueError('ambiguous tracked current WASM version') +VERSION = _versions[0].text +if not isinstance(VERSION, str) or not re.fullmatch(r'[0-9]+\.[0-9]+\.[0-9]+', VERSION): + raise ValueError('invalid tracked current WASM version') +SDK_ARCHIVE = f'fsgg-wasm-sdk-{VERSION}.tar.gz' MIB = 1024 * 1024 LIMITS = dict(total=512*MIB, file=256*MIB, logs=32*MIB, metadata=MIB, members=1024) MANIFEST_RESERVE = 256 * 1024 @@ -50,10 +61,10 @@ def fable_dependency(name): return ('library-runtime', 'fs.gg.wasm.browser', 'contentFiles/any/any/_content/FS.GG.Wasm.Browser/policy/fable_modules/'+relative) modules = { - 'FS.GG.Wasm.Browser.0.5.0': ('fs.gg.wasm.browser', + f'FS.GG.Wasm.Browser.{VERSION}': ('fs.gg.wasm.browser', ('Admission', 'Host', 'Invocation', 'Lifecycle', 'RuntimeProtocol', 'WorkerEntry'), 'FS.GG.Wasm.Browser'), - 'FS.GG.Wasm.Contracts.0.5.0': ('fs.gg.wasm.contracts', ('Contracts',), 'FS.GG.Wasm.Contracts')} + f'FS.GG.Wasm.Contracts.{VERSION}': ('fs.gg.wasm.contracts', ('Contracts',), 'FS.GG.Wasm.Contracts')} if module not in modules: raise ValueError('unreviewed Fable dependency module') package, stems, project = modules[module] if leaf in {stem+suffix for stem in stems for suffix in ('.fs', '.fsi')}: @@ -70,10 +81,10 @@ def fable_dependency(name): def consumer_member(name): safe_name(name) fixed={'consumer/release-manifest.json','consumer/NuGet.Config','consumer/consumer/packages.lock.json', - 'consumer/fsgg-wasm-sdk-0.5.0.tar.gz','consumer/installed-member-receipt.log','consumer/browser-result.json','consumer/http.log','consumer/public/sub/app/Consumer'} + 'consumer/'+SDK_ARCHIVE,'consumer/installed-member-receipt.log','consumer/browser-result.json','consumer/http.log','consumer/public/sub/app/Consumer'} if name in fixed:return True for ident in PACKAGES: - if name in {'consumer/packages/'+ident+'/'+leaf for leaf in (ident+'.0.5.0.nupkg',ident+'.0.5.0.nupkg.sha512','.nupkg.metadata')}:return True + if name in {'consumer/packages/'+ident+'/'+leaf for leaf in (f'{ident}.{VERSION}.nupkg',f'{ident}.{VERSION}.nupkg.sha512','.nupkg.metadata')}:return True if name in {'consumer/core/'+leaf for leaf in ('fsharp.core.10.1.302.nupkg','fsharp.core.10.1.302.nupkg.sha512','.nupkg.metadata','reference-netstandard2.0.dll','reference-netstandard2.1.dll')}:return True if '/fable_modules/' in name: context = name.split('/fable_modules/', 1)[0] @@ -130,7 +141,7 @@ def dependency(self, name): raise ValueError('pinned Fable library metadata drift') return context = 'consumer' if name.startswith('consumer/') else 'selected' - original = self.files[context+'/packages/'+package+'/'+package+'.0.5.0.nupkg'] + original = self.files[context+'/packages/'+package+'/'+f'{package}.{VERSION}.nupkg'] with zipfile.ZipFile(io.BytesIO(original)) as archive: rows = [row for row in archive.infolist() if row.filename == member] if len(rows) != 1 or rows[0].is_dir() or rows[0].file_size > LIMITS['file']: @@ -174,8 +185,8 @@ def tree(self, path, prefix): self.errors.append({'member':prefix,'status':'refused','reason':str(error)}) def packages(self, cache, prefix): for ident in PACKAGES: - base=pathlib.Path(cache)/ident/'0.5.0' - for leaf in (ident+'.0.5.0.nupkg',ident+'.0.5.0.nupkg.sha512','.nupkg.metadata'): + base=pathlib.Path(cache)/ident/VERSION + for leaf in (f'{ident}.{VERSION}.nupkg',f'{ident}.{VERSION}.nupkg.sha512','.nupkg.metadata'): self.add(base/leaf,prefix+'/'+ident+'/'+leaf) def core(self, cache, prefix): base=pathlib.Path(cache)/'fsharp.core/10.1.302' @@ -228,7 +239,7 @@ def consumer(projection, work): work=pathlib.Path(work) for name in ('release-manifest.json','NuGet.Config','consumer/packages.lock.json'): projection.add(work/name,'consumer/'+name) - projection.add(work/'sdk-artifacts/fsgg-wasm-sdk-0.5.0.tar.gz','consumer/fsgg-wasm-sdk-0.5.0.tar.gz') + projection.add(work/'sdk-artifacts'/SDK_ARCHIVE,'consumer/'+SDK_ARCHIVE) projection.add(work/'installed-member-receipt.log','consumer/installed-member-receipt.log') projection.add(work/'consumer-http.log','consumer/http.log') projection.packages(work/'consumer-packages','consumer/packages') @@ -324,11 +335,11 @@ def main(): if args.action=='consumer': consumer(p,args.work) else: merge(p,args.consumer); selected(p,args.work) - for name in ('release-manifest.json','promotion-binding.json','installed-input-audit.json','published-native.json','fsgg-wasm-sdk-0.5.0.tar.gz'): + for name in ('release-manifest.json','promotion-binding.json','installed-input-audit.json','published-native.json',SDK_ARCHIVE): p.add(pathlib.Path(args.receipts)/name,'receipts/'+name) for name in ('consumer.log','selected.log','runtime-status.json','consumer-status.json'): p.add(pathlib.Path(args.logs)/name,'execution/'+name) - for consumer_name,receipt_name in [('consumer/release-manifest.json','receipts/release-manifest.json'),('consumer/fsgg-wasm-sdk-0.5.0.tar.gz','receipts/fsgg-wasm-sdk-0.5.0.tar.gz')]: + for consumer_name,receipt_name in [('consumer/release-manifest.json','receipts/release-manifest.json'),('consumer/'+SDK_ARCHIVE,'receipts/'+SDK_ARCHIVE)]: if consumer_name not in p.files or receipt_name not in p.files or p.files[consumer_name]!=p.files[receipt_name]: p.errors.append({'member':consumer_name,'status':'absent/refused','reason':'consumer original differs from authenticated publication receipt'}) try: diff --git a/tests/release/wasm/test-installed-evidence.py b/tests/release/wasm/test-installed-evidence.py index 257f4d67..c4532261 100644 --- a/tests/release/wasm/test-installed-evidence.py +++ b/tests/release/wasm/test-installed-evidence.py @@ -11,9 +11,11 @@ import tarfile import tempfile import unittest +import xml.etree.ElementTree as ET from unittest.mock import patch sys.dont_write_bytecode=True ROOT=pathlib.Path(__file__).resolve().parents[3] +CURRENT=ET.parse(ROOT/'eng/wasm-shared/version.props').findtext('.//WasmSharedVersion') spec=importlib.util.spec_from_file_location('evidence',ROOT/'scripts/wasm-release/installed-evidence.py') e=importlib.util.module_from_spec(spec);spec.loader.exec_module(e) spec=importlib.util.spec_from_file_location('binder',ROOT/'scripts/wasm-release/bind-installed-release.py') @@ -24,18 +26,160 @@ def put(path, body=b'actual control bytes'): path.parent.mkdir(parents=True,exist_ok=True);path.write_bytes(body);return path +def current_cli_topology(root): + """Inert files for the actual two CLI actions; no file is executed as code. + + The maintained collector bytes and tracked version are copied unchanged to + an isolated source root so selected() reads its real fixed runtime paths. + This proves projection/caller composition, never installed/runtime proof. + """ + source=root/'source';script=source/'scripts/wasm-release/installed-evidence.py' + put(script,(ROOT/'scripts/wasm-release/installed-evidence.py').read_bytes()) + put(source/'eng/wasm-shared/version.props',(ROOT/'eng/wasm-shared/version.props').read_bytes()) + work=root/'consumer-work';selected=root/'selected';receipts=root/'receipts';logs=root/'logs' + manifest=json.dumps({'version':CURRENT}).encode();sdk=b'inert current SDK fixture' + for name,body in (('release-manifest.json',manifest),('NuGet.Config',b''), + ('consumer/packages.lock.json',b'{}'),('installed-member-receipt.log',b'fixture'), + ('consumer-http.log',b'fixture'),('consumer/browser/test-results/.last-run.json',b'{"status":"passed"}')): + put(work/name,body) + put(work/'sdk-artifacts'/f'fsgg-wasm-sdk-{CURRENT}.tar.gz',sdk) + for name in ('release-manifest.json','promotion-binding.json','installed-input-audit.json','published-native.json'): + put(receipts/name,manifest if name=='release-manifest.json' else b'{}') + put(receipts/f'fsgg-wasm-sdk-{CURRENT}.tar.gz',sdk) + put(root/'wasm-installed-receipts/release-manifest.json',manifest) + put(root/'wasm-installed-receipts/promotion-binding.json',b'{}') + archives={} + for ident in e.PACKAGES: + out=io.BytesIO() + with zipfile.ZipFile(out,'w') as z: + if ident=='fs.gg.wasm.browser': + z.writestr('fable/Host.fs',b'current Host source fixture') + z.writestr('contentFiles/any/any/_content/FS.GG.Wasm.Browser/policy/fable_modules/fable-library-js.5.18.0/Array.js',b'current library fixture') + else:z.writestr('fable/Contracts.fs',b'current Contracts source fixture') + archives[ident]=out.getvalue() + for cache in (work/'consumer-packages',selected/'packages'): + for ident,body in archives.items(): + for name,data in ((f'{ident}.{CURRENT}.nupkg',body),(f'{ident}.{CURRENT}.nupkg.sha512',b'fixture'),('.nupkg.metadata',b'{}')): + put(cache/ident/CURRENT/name,data) + for name in ('fsharp.core.10.1.302.nupkg','fsharp.core.10.1.302.nupkg.sha512','.nupkg.metadata', + 'lib/netstandard2.0/FSharp.Core.dll','lib/netstandard2.1/FSharp.Core.dll'): + put(cache/'fsharp.core/10.1.302'/name) + public=work/'consumer/public/sub/app' + for name in ('FSharp.Core.dll','Consumer.dll','Consumer','fable/Program.js'): + put(public/name) + for context in (public,root/'selected-browser'): + for name in ('module-worker.mjs','worker-client.mjs','worker-build.json','policy/WorkerEntry.js'): + put(context/'_content/FS.GG.Wasm.Browser'/name) + put(context/'_content/FS.GG.Wasm.Browser/policy/fable_modules/fable-library-js.5.18.0/Array.js',b'current library fixture') + for context in (public/'fable',selected/'installed-fable',root/'selected-browser/fable'): + put(context/'Program.js');put(context/'BrowserBridge.js') + put(context/f'fable_modules/FS.GG.Wasm.Browser.{CURRENT}/Host.fs',b'current Host source fixture') + put(context/f'fable_modules/FS.GG.Wasm.Contracts.{CURRENT}/Contracts.fs',b'current Contracts source fixture') + for name in ('limits-guest.wasm','http.log'):put(root/'selected-browser'/name) + for name in (*e.LOGS,'installed.packages.lock.json','installed.bad-hash.lock.json','official-core-joins.json'): + put(selected/name,b'{}' if name.endswith('.json') else b'fixture') + put(root/'selected-NuGet.Config',b'') + baseline=root/'selected-baseline' + for name in ('receipt.json','fsharp.core.10.1.302.nupkg','fsharp.core.10.1.401.nupkg','fsharp.core.10.1.203.nupkg', + 'FSharp.Core.10.1.302.netstandard2.0.dll','FSharp.Core.10.1.302.netstandard2.1.dll', + 'FSharp.Core.10.1.401.netstandard2.0.dll','FSharp.Core.10.1.401.netstandard2.1.dll','FSharp.Core.10.1.203.netstandard2.1.dll'): + put(baseline/'official-core'/name,b'{}' if name.endswith('.json') else b'fixture') + put(baseline/'compiler/FSharp.Core.dll') + put(baseline/'tools/.store/fable/5.18.0/fable/5.18.0/tools/net10.0/any/FSharp.Core.dll') + put(source/'tests/Wasm.Supervisor.Compatibility/browser/test-results/.last-run.json',b'{"status":"passed"}') + for name in ('InstalledCompatibility.dll','InstalledCompatibility.deps.json','InstalledCompatibility.runtimeconfig.json','FSharp.Core.dll'): + put(source/'tests/Wasm.Supervisor.Compatibility/Installed/bin/Debug/net10.0'/name) + for name,body in (('consumer.log',b'fixture'),('selected.log',b'fixture'),('runtime-status.json',b'{"exitCode":0}'), + ('consumer-status.json',b'{"runtime":0,"collection":0,"cleanup":0,"descendants":"unproved"}')): + put(logs/name,body) + return script,work,selected,receipts,logs + + class Evidence(unittest.TestCase): + def test_actual_current_cli_consumer_merge_and_assemble(self): + self.assertEqual(e.VERSION,CURRENT) + for defect in (None,'old-cache-only','different-authenticated-sdk'): + with self.subTest(defect=defect),tempfile.TemporaryDirectory() as raw: + root=pathlib.Path(raw);script,work,selected,receipts,logs=current_cli_topology(root) + if defect=='old-cache-only': + for ident in e.PACKAGES: + folder=work/'consumer-packages'/ident/CURRENT + folder.rename(folder.with_name('0.5.0')) + for leaf in folder.with_name('0.5.0').iterdir(): + if CURRENT in leaf.name:leaf.rename(leaf.with_name(leaf.name.replace(CURRENT,'0.5.0'))) + environment=dict(os.environ,RUNNER_TEMP=raw,RELEASE_VERSION='0.5.0',WASM_CANDIDATE_VERSION='0.5.0') + consumer=subprocess.run([sys.executable,str(script),'consumer','--work',str(work),'--output',str(root/'consumer-output'),'--runtime','0'],env=environment,capture_output=True,text=True) + manifest=json.loads((root/'consumer-output/evidence-manifest.json').read_text()) + if defect=='old-cache-only': + self.assertEqual(consumer.returncode,1);self.assertFalse(manifest['eligible']) + self.assertTrue(any(f'.{CURRENT}.nupkg' in row['member'] for row in manifest['errors'])) + continue + self.assertEqual(consumer.returncode,0,consumer.stderr);self.assertTrue(manifest['eligible']) + if defect:put(receipts/f'fsgg-wasm-sdk-{CURRENT}.tar.gz',b'wrong authenticated receipt fixture') + assembled=subprocess.run([sys.executable,str(script),'assemble','--work',str(selected),'--consumer',str(root/'consumer-output'), + '--receipts',str(receipts),'--logs',str(logs),'--output',str(root/'assembled'),'--runtime','0'],env=environment,capture_output=True,text=True) + final=json.loads((root/'assembled/evidence-manifest.json').read_text()) + self.assertEqual(assembled.returncode,int(defect is not None),assembled.stderr) + self.assertEqual(final['eligible'],defect is None) + if defect:self.assertTrue(any(row['reason']=='consumer original differs from authenticated publication receipt' for row in final['errors'])) + else: + self.assertFalse(final['errors']) + names={row['path'] for row in final['members']} + self.assertIn(f'receipts/fsgg-wasm-sdk-{CURRENT}.tar.gz',names) + self.assertIn(f'selected/packages/fs.gg.wasm.browser/fs.gg.wasm.browser.{CURRENT}.nupkg',names) + self.assertTrue(all('0.5.0' not in name for name in names)) + + def test_old_fable_and_package_names_are_not_current_fallbacks(self): + self.assertNotEqual(CURRENT,'0.5.0') + for project in ('FS.GG.Wasm.Browser','FS.GG.Wasm.Contracts'): + with self.assertRaisesRegex(ValueError,'unreviewed Fable dependency module'): + e.fable_dependency(f'consumer/public/sub/app/fable/fable_modules/{project}.0.5.0/Host.fs') + self.assertFalse(e.consumer_member('consumer/packages/fs.gg.wasm.browser/fs.gg.wasm.browser.0.5.0.nupkg')) + self.assertFalse(e.consumer_member('consumer/fsgg-wasm-sdk-0.5.0.tar.gz')) + + def test_ambiguous_or_invalid_tracked_version_refuses_before_collection(self): + for xml in ('','0.6.00.5.0', + 'current'): + with self.subTest(xml=xml),tempfile.TemporaryDirectory() as raw: + root=pathlib.Path(raw);script,work,*_=current_cli_topology(root) + put(root/'source/eng/wasm-shared/version.props',xml.encode()) + result=subprocess.run([sys.executable,str(script),'consumer','--work',str(work),'--output',str(root/'out'),'--runtime','0'],capture_output=True,text=True) + self.assertNotEqual(result.returncode,0) + self.assertFalse((root/'out').exists()) + + def test_actual_owner_trap_runs_current_collector_and_preserves_cleanup(self): + import shlex + source=(ROOT/'scripts/verify-wasm-package-consumer.sh').read_text() + trap=source.split('finish_consumer() {',1)[1].split('\ntrap finish_consumer EXIT',1)[0] + for missing in (False,True): + with self.subTest(missing=missing),tempfile.TemporaryDirectory() as raw: + root=pathlib.Path(raw);script,work,*_=current_cli_topology(root) + if missing: + (work/'consumer-packages/fs.gg.wasm.browser'/CURRENT/f'fs.gg.wasm.browser.{CURRENT}.nupkg').unlink() + status=root/'status.json';output=root/'consumer-output' + values={'mode':'--feed-only','repo':str(root/'source'),'work':str(work), + 'WASM_CONSUMER_EVIDENCE':str(output),'WASM_CONSUMER_STATUS':str(status)} + command='set -euo pipefail\n'+'\n'.join(key+'='+shlex.quote(value) for key,value in values.items()) + command+='\nfinish_consumer() {'+trap+'\ntrap finish_consumer EXIT\nexit 0\n' + result=subprocess.run(['bash','-c',command],capture_output=True,text=True) + self.assertEqual(result.returncode,int(missing),result.stderr) + actual=json.loads(status.read_text()) + self.assertEqual(actual,{'runtime':0,'collection':int(missing),'cleanup':0,'descendants':'unproved'}) + self.assertFalse(work.exists()) + merged=e.Projection();e.merge(merged,output) + self.assertEqual(bool(merged.errors),missing,merged.errors) + def test_hidden_metadata_and_colon_cache_exclusion(self): with tempfile.TemporaryDirectory() as raw: root=pathlib.Path(raw);work=root/'work' for name in ('release-manifest.json','NuGet.Config','consumer/packages.lock.json'): put(work/name,b'{}') - put(work/'sdk-artifacts/fsgg-wasm-sdk-0.5.0.tar.gz') + put(work/'sdk-artifacts'/f'fsgg-wasm-sdk-{CURRENT}.tar.gz') put(work/'installed-member-receipt.log') put(work/'consumer-http.log') for ident in e.PACKAGES: - for leaf in (ident+'.0.5.0.nupkg',ident+'.0.5.0.nupkg.sha512','.nupkg.metadata'): - put(work/'consumer-packages'/ident/'0.5.0'/leaf) + for leaf in (f'{ident}.{CURRENT}.nupkg',f'{ident}.{CURRENT}.nupkg.sha512','.nupkg.metadata'): + put(work/'consumer-packages'/ident/CURRENT/leaf) for name in ('fsharp.core.10.1.302.nupkg','fsharp.core.10.1.302.nupkg.sha512','.nupkg.metadata','lib/netstandard2.0/FSharp.Core.dll','lib/netstandard2.1/FSharp.Core.dll'): put(work/'consumer-packages/fsharp.core/10.1.302'/name) put(work/'consumer/browser/test-results/.last-run.json',b'{"status":"passed"}') @@ -80,7 +224,7 @@ def test_complete_fable_dependency_roles_and_original_archive_joins(self): with self.subTest(context=context),tempfile.TemporaryDirectory() as raw: root=pathlib.Path(raw);p=e.Projection();owner=context.split('/')[0] for package,body in archives.items(): - p.add(put(root/package,body),owner+'/packages/'+package+'/'+package+'.0.5.0.nupkg') + p.add(put(root/package,body),owner+'/packages/'+package+'/'+f'{package}.{CURRENT}.nupkg') dependency=root/'compiled/fable_modules' put(dependency/'.gitignore',b'*') library=dependency/'fable-library-js.5.18.0' @@ -88,7 +232,7 @@ def test_complete_fable_dependency_roles_and_original_archive_joins(self): ('package.json',b'{"name":"@fable-org/fable-library-js","version":"2.8.0","type":"module"}'), ('Array.js',b'actual runtime'),('lib/big.js',b'actual nested runtime')):put(library/leaf,body) for package,module in (('fs.gg.wasm.browser','FS.GG.Wasm.Browser'),('fs.gg.wasm.contracts','FS.GG.Wasm.Contracts')): - folder=dependency/(module+'.0.5.0') + folder=dependency/(module+'.'+CURRENT) for member,body in members[package].items(): if not member.startswith('fable/'):continue leaf=member[len('fable/'):] @@ -97,13 +241,13 @@ def test_complete_fable_dependency_roles_and_original_archive_joins(self): if leaf.endswith('.fs'):put(folder/(leaf+'.js'),b'generated current source') p.tree(root/'compiled',context) self.assertFalse(p.errors,p.errors) - self.assertIn(context+'/fable_modules/FS.GG.Wasm.Browser.0.5.0/Host.fs',p.files) - self.assertIn(context+'/fable_modules/FS.GG.Wasm.Browser.0.5.0/Host.fs.js',p.files) + self.assertIn(context+f'/fable_modules/FS.GG.Wasm.Browser.{CURRENT}/Host.fs',p.files) + self.assertIn(context+f'/fable_modules/FS.GG.Wasm.Browser.{CURRENT}/Host.fs.js',p.files) if owner=='consumer': self.assertEqual(p.finish(root/'out',0),0) joined=e.Projection();e.merge(joined,root/'out') self.assertFalse(joined.errors,joined.errors);self.assertEqual(joined.files,p.files) - copied=context+'/fable_modules/FS.GG.Wasm.Browser.0.5.0/Host.fs' + copied=context+f'/fable_modules/FS.GG.Wasm.Browser.{CURRENT}/Host.fs' original=p.files[copied];p.files[copied]=b'changed copied source' p.finish(root/'forged',0) refused=e.Projection();e.merge(refused,root/'forged') @@ -118,8 +262,8 @@ def test_complete_fable_dependency_roles_and_original_archive_joins(self): bad.dependency(context+'/fable_modules/fable-library-js.5.18.0/Array.js') self.assertTrue(any(x['reason']=='Fable dependency differs from original package member' for x in bad.errors),bad.errors) for leaf in ('fable-library-js.5.18.0/unexpected.exe','fable-library-js.5.18.0/foreign.js', - 'Other.0.5.0/Host.fs','FS.GG.Wasm.Browser.0.5.0/Foreign.fs', - 'FS.GG.Wasm.Browser.0.5.0/Foreign.fs.js'): + f'Other.{CURRENT}/Host.fs',f'FS.GG.Wasm.Browser.{CURRENT}/Foreign.fs', + f'FS.GG.Wasm.Browser.{CURRENT}/Foreign.fs.js'): name=context+'/fable_modules/'+leaf if leaf.endswith('foreign.js'): bad.files[name]=b'foreign runtime';bad.dependency(name) @@ -184,13 +328,14 @@ def test_owner_trap_retains_original_status_after_collection_failure(self): trap=source.split('finish_consumer() {',1)[1].split('\ntrap finish_consumer EXIT',1)[0] # Run only the maintained shell trap body with inert collection/cleanup # functions. No consumer command or downloaded source is executed. - for runtime,collection,expected in ((37,19,37),(0,19,19),(0,0,0)): + for runtime,collection,cleanup,expected in ((37,19,23,37),(0,19,23,19),(0,0,23,23),(0,0,0,0)): with tempfile.TemporaryDirectory() as raw: - script='set -euo pipefail\nmode=--feed-only\nrepo=/inert\nwork=/inert\nWASM_CONSUMER_EVIDENCE=/inert\nWASM_CONSUMER_STATUS='+raw+'/status.json\npython3(){ return '+str(collection)+'; }\nrm(){ return 0; }\nfinish_consumer() {'+trap+'\ntrap finish_consumer EXIT\nexit '+str(runtime) + script='set -euo pipefail\nmode=--feed-only\nrepo=/inert\nwork=/inert\nWASM_CONSUMER_EVIDENCE=/inert\nWASM_CONSUMER_STATUS='+raw+'/status.json\npython3(){ return '+str(collection)+'; }\nrm(){ return '+str(cleanup)+'; }\nfinish_consumer() {'+trap+'\ntrap finish_consumer EXIT\nexit '+str(runtime) result=subprocess.run(['bash','-c',script],capture_output=True,text=True) self.assertEqual(result.returncode,expected,result.stderr) status=json.loads((pathlib.Path(raw)/'status.json').read_text()) self.assertEqual(status['runtime'],runtime);self.assertEqual(status['collection'],collection) + self.assertEqual(status['cleanup'],cleanup) def test_workflow_preserves_command_first_cause_and_native_log_failure(self): text=(ROOT/'.github/workflows/wasm-installed-org.yml').read_text() @@ -274,16 +419,31 @@ def test_complete_diff_binds_exact_blobs_and_refuses_semantic_change(self): root=pathlib.Path(raw) def git(*args):return subprocess.check_output(['git','-C',raw,*args],text=True,stderr=subprocess.DEVNULL).strip() git('init','-q');git('config','user.name','inert control');git('config','user.email','control@example.invalid') - allowed='scripts/wasm-release/bind-installed-release.py' - put(root/allowed,b'old');git('add','.');git('commit','-qm','P');p=git('rev-parse','HEAD');tree=git('rev-parse','HEAD^{tree}') - put(root/allowed,b'new');git('add','.');git('commit','-qm','Q');q=git('rev-parse','HEAD') + allowed=('scripts/wasm-release/installed-evidence.py','tests/release/wasm/test-installed-evidence.py') + excluded=('eng/wasm-shared/version.props','src/Wasm.Browser/Host.fs','sdk/wasm/verify-sdk.sh', + '.github/workflows/wasm-installed-org.yml','scripts/wasm-release/promotion.py','unlisted.txt') + for name in (*allowed,*excluded):put(root/name,b'old') + git('add','.');git('commit','-qm','P');p=git('rev-parse','HEAD');tree=git('rev-parse','HEAD^{tree}') + for name in allowed:put(root/name,b'new') + git('add','.');git('commit','-qm','Q');q=git('rev-parse','HEAD') with patch.object(b,'ROOT',root),patch.object(b,'PUBLISHED',p),patch.object(b,'PUBLISHED_TREE',tree): - audit=b.source_audit(p,q);self.assertEqual(len(audit['changes']),1) + audit=b.source_audit(p,q);self.assertEqual({row['path'] for row in audit['changes']},set(allowed)) row=audit['changes'][0];self.assertEqual(row['new']['sha256'],e.hashlib.sha256(b'new').hexdigest()) - self.assertEqual(row['old']['blob'],git('rev-parse',p+':'+allowed)) - put(root/'src/Wasm.Browser/Host.fs',b'semantic mutation');git('add','.');git('commit','-qm','bad') - with self.assertRaises(AssertionError):b.source_audit(p,git('rev-parse','HEAD')) + self.assertEqual(row['old']['blob'],git('rev-parse',p+':'+row['path'])) + with patch.object(b,'INSTALLED_PATHS',b.INSTALLED_PATHS-{allowed[0]}),self.assertRaises(AssertionError):b.source_audit(p,q) + for name in excluded: + git('reset','--hard',q);put(root/name,b'forbidden change');git('add','.');git('commit','-qm','excluded leaf') + with self.subTest(excluded=name),self.assertRaises(AssertionError):b.source_audit(p,git('rev-parse','HEAD')) + for defect in ('mode','delete','rename','symlink'): + git('reset','--hard',q);path=root/allowed[0] + if defect=='mode':path.chmod(0o755) + elif defect=='delete':path.unlink() + elif defect=='rename':path.rename(path.with_name('renamed.py')) + else:path.unlink();path.symlink_to(root/allowed[1]) + git('add','.');git('commit','-qm',defect) + with self.subTest(defect=defect),self.assertRaises(AssertionError):b.source_audit(p,git('rev-parse','HEAD')) with self.assertRaises(AssertionError):b.source_audit('a'*40,q) + with patch.object(b,'PUBLISHED_TREE','a'*40),self.assertRaises(AssertionError):b.source_audit(p,q) if __name__=='__main__':unittest.main() From 2e764dff2021d368d23041e9fd84c49b3514f89b Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sun, 18 Oct 2026 04:17:39 +0200 Subject: [PATCH 2/2] Capture finite collector correction and bind its exact evidence leaf --- docs/atlas/catalog.json | 2 +- ...91eff27f59b569a2d42a18dc0c134d4935d52d802c5a71b8a594281.json | 1 + scripts/wasm-release/bind-installed-release.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/atlas/records/5466c617291eff27f59b569a2d42a18dc0c134d4935d52d802c5a71b8a594281.json diff --git a/docs/atlas/catalog.json b/docs/atlas/catalog.json index 13c5cdcf..9192144a 100644 --- a/docs/atlas/catalog.json +++ b/docs/atlas/catalog.json @@ -1 +1 @@ -{"records":["049f1d0d8155f65b6f7675ebdb200d4595570cc7d585dd74876e0285eceffed6","0863093e69a16355fa9b90f8e7e29156ea4ad88c4523bc4883568bde831bdf35","0b92e4db5e34990113bb43cb8cd58cf251d06083cf812398d6da9fdebb98e0f5","2056fa8b653c2240afdad2a16f572a07019109254847829610a9468bb2f95248","231df3792931ad2333d00970a5993b3e13fa9dc38f687899f9fa2c966ed9fbfa","24fdc3980640ba58bedcae6f16d5b467eb7383c31161fb9b6ff10fd2e667b0e5","264a2f7887a9b6473cbc24102e1bf9be70e57f6d7ba224138140efa094d541f4","2b7dfb3a2f76368cb603161cdb6f8fa8c2b8afc3e96659dd37c975921ba5ba66","33df463263533529cca24a0b09937573f836d197cc1a739e9960b1cdd4deb1fd","343077d02ddd266b21d8677f7c256854134f7e3e2d18137b55ebe3f32564c5c0","34391bda3fb56135a2bddf74ab790958c175bc6a69c38175e6f8db792cfd50a0","36a19fefd7024f54f3391ffed19019c10222a514719edd7a44f0cde2a1c48258","3d3a0a302c02cd0bfee86715d09b0211baa8dad3e47402c638fc95c7cfa02e87","3de8718fb3ca9b15212e8bd84fba205995d24636087090a61c63c28a96a745b1","3df4f31fda99ad137c651322ad29809e36380400516399274870b4a4332b4a92","3f09ee199e3c2e808e0f6e74f197f2fdd8158ae099a7195c498ce9466bb78b03","44bda82d6f9a9625a83283bf56a15a1831f287037214dc134eeeae87f1faa869","6c5b38ee260d1f15f8029d699ec1cebceafb5e1407a6341dc7a4b96f4d9d3521","71bc29a175940d22dd1d85148ee49f47aed714395fbd51d07b8751147815a55f","72742cbaa561237f10251e94aa5c61153ab5c7f14fc5771b7a5ebdece1e1cb11","7eba935f363db90d4b6d3fea2d226a7a58b08b88cf748e619ddb407ea83911e9","7f62ef13b4b46b4f23cae9536c977c15c85320a253ed2bc609eb7a1f0afdcf56","85f06991da74fa4e59ba73c7034dc53a35a4e90c5b64d83cc8956d3afd61d376","87d6de6f3730cecd75d9a4411bf4dbac4e058127a3ce9f46b07f6de110bc26de","8b1cf51c5f03c0ae19c46ca3ea4a1158acbb6e47bf109ca8c506c5417b51cf9f","8c514796f190fe6d4307be57983c84f92208f5d2f00fcbe3093b72618f2d7bc1","95115e1052b5ea4fcd393f5c9b9c6f0298657f0028eee84e16215ede97576cfa","9b6c05923a43ea37b10504396105d97ba51c0430e82fb3bb4953ee4256cdc1aa","a38737476856236f33e0aef1b97e2b900677cf99f1dbcfd381c708ceeca8b7f3","ab4502eff76606ac8e58b623878f3d76e1e0b18f30796aec7c446e2aab8b57c8","ab69ebe00eac40c68aec6131ef0559967b399266aae89f9673d3c30ce12f36b0","be492023350541da5256d6f653711b5f736d5216cf121a7e02ca43e1887215e4","c01c2c98f13a65b49113916a2f295e46a03fc4c185ece880267dd86c3f3dee8b","d557525e3ebfad227c8d6db9bb6350d42506fb02b1de8058cb443f4f008c0f67","d9718eb12e9ae9ea3a29a9904f131f53e99c584ef1b297aa05f1f8f6cdc6ab2a","fa8cd29fe962d7e853457e829c2d5927685f0ff374e6450fff5529f785db442d"],"schema":"fsgg.atlas/1"} +{"records":["049f1d0d8155f65b6f7675ebdb200d4595570cc7d585dd74876e0285eceffed6","0863093e69a16355fa9b90f8e7e29156ea4ad88c4523bc4883568bde831bdf35","0b92e4db5e34990113bb43cb8cd58cf251d06083cf812398d6da9fdebb98e0f5","2056fa8b653c2240afdad2a16f572a07019109254847829610a9468bb2f95248","231df3792931ad2333d00970a5993b3e13fa9dc38f687899f9fa2c966ed9fbfa","24fdc3980640ba58bedcae6f16d5b467eb7383c31161fb9b6ff10fd2e667b0e5","264a2f7887a9b6473cbc24102e1bf9be70e57f6d7ba224138140efa094d541f4","2b7dfb3a2f76368cb603161cdb6f8fa8c2b8afc3e96659dd37c975921ba5ba66","33df463263533529cca24a0b09937573f836d197cc1a739e9960b1cdd4deb1fd","343077d02ddd266b21d8677f7c256854134f7e3e2d18137b55ebe3f32564c5c0","34391bda3fb56135a2bddf74ab790958c175bc6a69c38175e6f8db792cfd50a0","36a19fefd7024f54f3391ffed19019c10222a514719edd7a44f0cde2a1c48258","3d3a0a302c02cd0bfee86715d09b0211baa8dad3e47402c638fc95c7cfa02e87","3de8718fb3ca9b15212e8bd84fba205995d24636087090a61c63c28a96a745b1","3df4f31fda99ad137c651322ad29809e36380400516399274870b4a4332b4a92","3f09ee199e3c2e808e0f6e74f197f2fdd8158ae099a7195c498ce9466bb78b03","44bda82d6f9a9625a83283bf56a15a1831f287037214dc134eeeae87f1faa869","5466c617291eff27f59b569a2d42a18dc0c134d4935d52d802c5a71b8a594281","6c5b38ee260d1f15f8029d699ec1cebceafb5e1407a6341dc7a4b96f4d9d3521","71bc29a175940d22dd1d85148ee49f47aed714395fbd51d07b8751147815a55f","72742cbaa561237f10251e94aa5c61153ab5c7f14fc5771b7a5ebdece1e1cb11","7eba935f363db90d4b6d3fea2d226a7a58b08b88cf748e619ddb407ea83911e9","7f62ef13b4b46b4f23cae9536c977c15c85320a253ed2bc609eb7a1f0afdcf56","85f06991da74fa4e59ba73c7034dc53a35a4e90c5b64d83cc8956d3afd61d376","87d6de6f3730cecd75d9a4411bf4dbac4e058127a3ce9f46b07f6de110bc26de","8b1cf51c5f03c0ae19c46ca3ea4a1158acbb6e47bf109ca8c506c5417b51cf9f","8c514796f190fe6d4307be57983c84f92208f5d2f00fcbe3093b72618f2d7bc1","95115e1052b5ea4fcd393f5c9b9c6f0298657f0028eee84e16215ede97576cfa","9b6c05923a43ea37b10504396105d97ba51c0430e82fb3bb4953ee4256cdc1aa","a38737476856236f33e0aef1b97e2b900677cf99f1dbcfd381c708ceeca8b7f3","ab4502eff76606ac8e58b623878f3d76e1e0b18f30796aec7c446e2aab8b57c8","ab69ebe00eac40c68aec6131ef0559967b399266aae89f9673d3c30ce12f36b0","be492023350541da5256d6f653711b5f736d5216cf121a7e02ca43e1887215e4","c01c2c98f13a65b49113916a2f295e46a03fc4c185ece880267dd86c3f3dee8b","d557525e3ebfad227c8d6db9bb6350d42506fb02b1de8058cb443f4f008c0f67","d9718eb12e9ae9ea3a29a9904f131f53e99c584ef1b297aa05f1f8f6cdc6ab2a","fa8cd29fe962d7e853457e829c2d5927685f0ff374e6450fff5529f785db442d"],"schema":"fsgg.atlas/1"} diff --git a/docs/atlas/records/5466c617291eff27f59b569a2d42a18dc0c134d4935d52d802c5a71b8a594281.json b/docs/atlas/records/5466c617291eff27f59b569a2d42a18dc0c134d4935d52d802c5a71b8a594281.json new file mode 100644 index 00000000..659f2cd9 --- /dev/null +++ b/docs/atlas/records/5466c617291eff27f59b569a2d42a18dc0c134d4935d52d802c5a71b8a594281.json @@ -0,0 +1 @@ +{"citations":[{"path":"docs/roadmaps/wasm-shared-01.md","repository":"FS-GG/FS.GG.Game","revision":"d492d37b711d0767db8179d84a062d6a6107c990","sha256":"dd2504b52a261ceba43671ad411323417b4063d3405a9e810badcbdacfb9e636"},{"path":"scripts/wasm-release/bind-installed-release.py","repository":"FS-GG/FS.GG.Game","revision":"d492d37b711d0767db8179d84a062d6a6107c990","sha256":"902739145ec2a456c08420edff822a4dcafb7a375931a19e104d584a97a5dbf8"},{"path":"scripts/wasm-release/installed-evidence.py","repository":"FS-GG/FS.GG.Game","revision":"d492d37b711d0767db8179d84a062d6a6107c990","sha256":"b7b71f15d8a2ea33934d9165b74d40e892e73b55d1f371656b234f61a4e9b53d"},{"path":"tests/release/wasm/test-installed-evidence.py","repository":"FS-GG/FS.GG.Game","revision":"d492d37b711d0767db8179d84a062d6a6107c990","sha256":"fc4e97a0be7da935df29a11e256d2e51358e5b447a0c82d8b5e1c18218fce988"},{"path":"tests/release/wasm/test-installed-org-workflow.py","repository":"FS-GG/FS.GG.Game","revision":"d492d37b711d0767db8179d84a062d6a6107c990","sha256":"c57fd29abcbe9e87e6848c3835751bf35333bae61a2df01c2db027dba7df8ff3"},{"path":"tests/release/wasm/test-release-promotion.py","repository":"FS-GG/FS.GG.Game","revision":"d492d37b711d0767db8179d84a062d6a6107c990","sha256":"e0655c8274b4d9eba1ae53709324e037fa6e2d0985b0039f4692add6cd7cf3a3"},{"path":"scripts/verify-wasm-package-consumer.sh","repository":"FS-GG/FS.GG.Game","revision":"8a5e63ba50d4b3cb7b5a66bb4136990d5589ee9e","sha256":"31d98f934faee58ade6b84ed776bfd344435601b3a5f6741877e45781ce953ac"},{"path":"scripts/wasm-release/qualify-supervisor-installed.sh","repository":"FS-GG/FS.GG.Game","revision":"8a5e63ba50d4b3cb7b5a66bb4136990d5589ee9e","sha256":"a45fb4432a367edf631d8a013ff85022d57a46c84d2fec848fd37053023b8893"},{"path":".github/workflows/wasm-installed-org.yml","repository":"FS-GG/FS.GG.Game","revision":"8a5e63ba50d4b3cb7b5a66bb4136990d5589ee9e","sha256":"5d9a89a860e6af539ce4c5e99add2a2d5cc50e217ae36b720a2b8f90a9434221"},{"path":"sdk/wasm/build-source-archive.sh","repository":"FS-GG/FS.GG.Game","revision":"8a5e63ba50d4b3cb7b5a66bb4136990d5589ee9e","sha256":"24dfc2ef6f8ed0a0c029980e25c2ee578beb0cca7abe22964a2fe2bc3cc9ce09"},{"path":"scripts/wasm-release/promotion.py","repository":"FS-GG/FS.GG.Game","revision":"8a5e63ba50d4b3cb7b5a66bb4136990d5589ee9e","sha256":"d2ea88920b22e30df973765085c92116685dda689616045bb3b01080bd5d848d"},{"path":".github/workflows/release-wasm.yml","repository":"FS-GG/FS.GG.Game","revision":"8a5e63ba50d4b3cb7b5a66bb4136990d5589ee9e","sha256":"8a914479ddb212047706e6ec51440a7425c68df53a8cb5a25c8f4d14cc8fdf17"}],"conflictsWith":[],"id":"game/current-wasm-custody-source-boundary","kind":"boundary","recordedAt":"2026-10-18T02:17:20Z","repository":"FS-GG/FS.GG.Game","schema":"fsgg.atlas/1","status":"recorded","summary":"Immutable published0.6 remains P8a5e63ba50d4b3cb7b5a66bb4136990d5589ee9e/tree090906c22f0ef0cd3ca453a28ca20ce8686e0753, protected39084881928/artifact12120591664 and accepted publisher39085989107/a1/release416167085/transactionfa7a48d1b389116b18f00b5ec94dd83e82bfd3fd9e4032fe052eb13eab019119. Manifest e40258fe5cf6471c868afe0f6405d482a5a60ccfdb192f8cdb36b36adf08b8ff and SDK b06bdd4a75e9f9727f58cdbeb272e42f1c88607f2326152c67feb5dc5aeccd6f remain exact P originals. The served SDK contains SDK/examples, not installed collector/binder/qualification helpers; changed Q collection code must never be compared to, inserted into or repacked as P SDK payload.\n\nPR724 delivered Q025cbfd4b3efed0a3baf87c52ebae404124cd814/tree4fc6eb4b9d745d1f0869fa32a411ea00a7f09448 after accepted source original39086999965/a1. First selected served-feed installed39088631914/a1 then failed both feeds. Each actual consumer compiled and passed36 browser cases; authoritative consumer status is runtime0/collection1/directcleanup0/descendantsunproved. Selected-supervisor qualification was unreached. The first recorded evidence refusal is actual Fable library Array.js trying to join nonexistent fs.gg.wasm.browser.0.5.0.nupkg. Fixed5 names also affected package cache, Fable package modules, SDK, merge/assembly and authenticated receipt equality. Existing trap correctly preserved collection failure after runtime success. Native artifacts org12122285091/SHAf79eefd4ec6cdb91f4e14fa641ec29e0777c68f859019dd5d72aa00993feb792 and public12121452710/SHAa5541e625500eba0112c7edeff5db6e6c3462bcc1b20b9762df637f1409a7f1d retain204members each and are ineligible. Independent failed-original review corroborates exact native identities, statuses and representative published Worker bytes; no installed acceptance or first-cause substitution follows. Original failure is consumed, not retried; its temporary source hold ended only after actual terminal/readback disposition.\n\nThe selected finite qualifier correction derives one unambiguous stable VERSION from audited tracked version.props, without environment/CLI selectors, stale-cache inference or old5 fallback. One SDK_ARCHIVE and exact current package/module/cache names are used throughout consumer, selected original-member joins, Fable roles, merge and assembled receipt equality. Fable5.18, Core official TFM roles, exact module stems/contexts, mandatory originals, byte equality, regular/single-link files and512MiB total/256MiB file/32MiB logs/1MiB metadata/1024member bounds remain unchanged. All producer/runtime/version.props/SDK/workflow/promotion/pin inputs remain P-identical; collector/binder qualification inputs belong to selected Q, not P.\n\nAuthority is the existing immutable P/tree, ancestry, complete raw no-rename path/mode/blob/hash audit and actual clean current Q=GITHUB_SHA=GITHUB_WORKFLOW_SHA=ACCEPTED_EXECUTOR/QUALIFIER_SOURCE on genuine main/manual-event/attempt1/installed job/feed. The finite P-to-successor-Q closure retains the prior seven exact leaves, adds only installed-evidence.py/test-installed-evidence.py and this exact new capture leaf. No prefix/glob, arbitrary descendant, historical executor overlay, publisher/repack or runtime permission is admitted. Source audit establishes correspondence; independent exact-source/root review and actual native admission still establish the selected invocation.\n\nActual current CLI consumer→sidecar/tar→merge→selected→assemble controls use unchanged maintained script bytes and declared synthetic archives/runtime-shaped files, never executed fixtures. They exercise original Array.js/current archive provenance, hostile5 environment, old-only cache/Fable/SDK refusals and unequal authenticated SDK bytes. The actual EXIT trap invokes the real collector, deletes only private fixture work and preserves runtime/collection/cleanup/merged eligibility; independent controlled failure ports retain runtime-first, then collection, then cleanup and command/log priorities. Real source_audit controls cover exact collector/test positive, missing allowlist entry, excluded version/runtime/SDK/workflow/promotion/arbitrary leaves, mode/delete/rename/link and wrong P/tree. Affected source-mapping/workflow and18inert collector tests pass. Early broad local preflight on a dirty tree and extra256MiB address-space ceiling refused; clean unchanged37promotion controls subsequently passed in1.58s/~42MiB peak RSS with a compatible local data-read ceiling. No native collector/payload cap was raised.\n\nWhole connected source review passes for this prepared correction; exact final capture/leaf audit and root authority review precede ordinary source admission. Fresh current source compilation/coherentCI, qualified source delivery and separately selected both-feed installed operation remain required; no unchanged rerun is selected. Automatic protectedQ39088390588 remains independent evidence. Last accepted served installed producer remains0.5 Qc938/original39076218708; current0.6 publication/source/consumer runtime passes do not establish installed0.6, product/native/image/wholeS1, descendant settlement, old timeout cause or future timing fit. Original logs/archives stay in restricted custody rather than Atlas.","supersedes":["be492023350541da5256d6f653711b5f736d5216cf121a7e02ca43e1887215e4"],"tags":["wasm","qualification","current-only","custody","collector","publication","first-cause"],"title":"Keep published 0.6 originals immutable while repairing finite current qualifier evidence"} diff --git a/scripts/wasm-release/bind-installed-release.py b/scripts/wasm-release/bind-installed-release.py index 4c7e5bf1..ae5beb06 100644 --- a/scripts/wasm-release/bind-installed-release.py +++ b/scripts/wasm-release/bind-installed-release.py @@ -27,6 +27,7 @@ 'docs/atlas/catalog.json', 'docs/atlas/records/7f62ef13b4b46b4f23cae9536c977c15c85320a253ed2bc609eb7a1f0afdcf56.json', 'docs/atlas/records/be492023350541da5256d6f653711b5f736d5216cf121a7e02ca43e1887215e4.json', + 'docs/atlas/records/5466c617291eff27f59b569a2d42a18dc0c134d4935d52d802c5a71b8a594281.json', })