From f4afab9f7cb202f65ddc2a728319c8223bba4fc8 Mon Sep 17 00:00:00 2001 From: Dr Alexander Mikhalev Date: Wed, 14 Oct 2026 15:28:44 +0100 Subject: [PATCH] fix: validate producer log names for LSP RC6 --- crates/terraphim_lsp/Cargo.lock | 2 +- crates/terraphim_lsp/Cargo.toml | 2 +- docs/plans/public-lsp-packages.md | 23 ++++++++++++++++++-- release/lsp-crates.json | 2 +- scripts/lsp_public_binary.py | 4 +++- tests/test_lsp_public_binary.py | 35 +++++++++++++++++++++++++++---- 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/crates/terraphim_lsp/Cargo.lock b/crates/terraphim_lsp/Cargo.lock index c71c698a6..011111d14 100644 --- a/crates/terraphim_lsp/Cargo.lock +++ b/crates/terraphim_lsp/Cargo.lock @@ -1597,7 +1597,7 @@ dependencies = [ [[package]] name = "terraphim_lsp" -version = "1.22.0-rc.5" +version = "1.22.0-rc.6" dependencies = [ "criterion", "env_logger", diff --git a/crates/terraphim_lsp/Cargo.toml b/crates/terraphim_lsp/Cargo.toml index 0070165f3..292967c3b 100644 --- a/crates/terraphim_lsp/Cargo.toml +++ b/crates/terraphim_lsp/Cargo.toml @@ -50,7 +50,7 @@ resolver = "2" members = [".", "../terraphim_lsp_core"] [workspace.package] -version = "1.22.0-rc.5" +version = "1.22.0-rc.6" edition = "2024" rust-version = "1.91" diff --git a/docs/plans/public-lsp-packages.md b/docs/plans/public-lsp-packages.md index c9e862fc3..404e29351 100644 --- a/docs/plans/public-lsp-packages.md +++ b/docs/plans/public-lsp-packages.md @@ -20,7 +20,7 @@ change. The original `alternative.add` result remains unchanged. - `terraphim_lsp_core` is version `1.23.1`, with public dependencies on types `1.22.3`, automata `2.1.0`, alternatives `0.1.1` and Lab `0.1.1`. -- `terraphim_lsp` is the `1.22.0-rc.5` candidate and depends on core `1.23.1`. +- `terraphim_lsp` is the `1.22.0-rc.6` candidate and depends on core `1.23.1`. - Both packages restrict publication to crates.io and include the Apache-2.0 licence notice and their own test fixtures. No private Git history, registry credentials or operational files are part of the snapshot. @@ -64,7 +64,7 @@ outside the source checkout. A second job reproduces the qualified archive before requesting publication credentials. Core `terraphim_lsp_core-v1.23.1` is already published. The next server tag is -`terraphim_lsp-v1.22.0-rc.5`; the published RC4 crate and source tag remain immutable. Existing crates use the `crates-io` environment and +`terraphim_lsp-v1.22.0-rc.6`; the published RC4/RC5 crates and source tags remain immutable. Existing crates use the `crates-io` environment and crates.io trusted publishing. A first-ever crate name uses a deliberate manual run on its exact tag with `auth_mode=bootstrap` and the `crates-io-bootstrap` environment. This temporary path reads the existing `CARGO_REGISTRY_TOKEN` @@ -126,3 +126,22 @@ and synonym-export protocol checks; ARM64 was architecture-checked without a native runtime claim. These diagnostic outputs are not published release assets. RC5 changes the server release identity and build helper; core 1.23.1 remains unchanged. The normal release workflow must qualify and publish the new source. + +## Build-log filename correction + +RC5 successfully published its crate and built all six binaries. Final assembly +then rejected real log names such as `build-x86_64-unknown-linux-musl.log`: the +validator excluded underscores even though the producer uses target triples in +its filenames. The validator now accepts only the exact `fetch-.log` +and `build-.log` names for each known target, or `universal.log` for the +combined macOS asset. Test fixtures use these actual producer names, and the +full saved RC5 artifact set is reassembled locally as regression evidence. + +RC6 uses the established tag/OIDC workflow with this validator correction; +server runtime code and core 1.23.1 remain unchanged. RC5 diagnostic assembly +outputs are not published binaries. The previous crate and tag remain intact. + +The current workflow names producer artifacts by run attempt. A failed-publish- +only retry would request a newer attempt's artifacts that were never produced; +such a retry is not a supported recovery route. Do not move a tag, replace +artifacts, or relax protected-environment rules to recover a publication. diff --git a/release/lsp-crates.json b/release/lsp-crates.json index a70b781a7..2f94ba6a2 100644 --- a/release/lsp-crates.json +++ b/release/lsp-crates.json @@ -12,7 +12,7 @@ }, { "name": "terraphim_lsp", - "version": "1.22.0-rc.5", + "version": "1.22.0-rc.6", "path": "crates/terraphim_lsp/Cargo.toml" } ] diff --git a/scripts/lsp_public_binary.py b/scripts/lsp_public_binary.py index 29446187e..ecac10e87 100644 --- a/scripts/lsp_public_binary.py +++ b/scripts/lsp_public_binary.py @@ -428,9 +428,11 @@ def bundle(folder, qualification, expected, write=False): require(protocol == {'status': 'NOT_RUN_CROSS_TARGET'}, 'Unexecuted target has misleading protocol evidence') commands = record.get('commands') require(isinstance(commands, list) and commands, 'Missing executed build commands') + allowed_logs = {'universal.log'} if target.startswith('universal') else { + 'fetch-' + target + '.log', 'build-' + target + '.log'} for command_record in commands: require(isinstance(command_record, dict) and isinstance(command_record.get('log'), str) - and re.fullmatch(r'[a-z0-9-]+\.log', command_record['log']), 'Invalid build log name') + and command_record['log'] in allowed_logs, 'Invalid build log name') log = folder / 'logs' / target / command_record['log'] require(command_record.get('exitCode') == 0 and log.is_file() and not log.is_symlink() and publisher.digest(log) == command_record.get('sha256'), 'Build log differs') diff --git a/tests/test_lsp_public_binary.py b/tests/test_lsp_public_binary.py index 9db6d2972..c92aa8523 100644 --- a/tests/test_lsp_public_binary.py +++ b/tests/test_lsp_public_binary.py @@ -67,12 +67,17 @@ def fixture_bundle(self): target=name.removeprefix('terraphim-lsp-');path=folder/name;path.write_bytes(self.executable(target)) for name in b.RAW: target=name.removeprefix('terraphim-lsp-');path=folder/name;logs=folder/'logs'/target;logs.mkdir(parents=True) - log=logs/'build.log';log.write_text('Fixture build log\n') + log=logs/('universal.log' if target.startswith('universal') else 'build-'+target+'.log');log.write_text('Fixture build log\n') argv=['rustup','run','1.96','cargo','build','--locked','--offline','--release','--target',target,'--bin','terraphim-lsp'] extra={} if target.startswith('universal'): argv=['lipo','-create'];extra={'inputHashes':{n:b.publisher.digest(folder/n) for n in b.RAW if n in ('terraphim-lsp-aarch64-apple-darwin','terraphim-lsp-x86_64-apple-darwin')}} - record={'schema':1,'status':'PASS','source':source,'target':target,'asset':{'name':name,'sha256':b.publisher.digest(path),'bytes':path.stat().st_size},'architectures':b.architecture(path),'host':{'system':'Fixture','machine':'Fixture'},'nativeVersion':{'status':'PASS','exitCode':0,'stdout':'terraphim-lsp 1.22.0-rc.4\n'},'commands':[{'argv':argv,'exitCode':0,'log':log.name,'sha256':b.publisher.digest(log)}],'extra':extra} + build_commands=[] + if not target.startswith('universal'): + fetched=logs/('fetch-'+target+'.log');fetched.write_text('Fixture fetch log\n') + build_commands.append({'argv':['rustup','run','1.96','cargo','fetch','--locked','--target',target],'exitCode':0,'log':fetched.name,'sha256':b.publisher.digest(fetched)}) + build_commands.append({'argv':argv,'exitCode':0,'log':log.name,'sha256':b.publisher.digest(log)}) + record={'schema':1,'status':'PASS','source':source,'target':target,'asset':{'name':name,'sha256':b.publisher.digest(path),'bytes':path.stat().st_size},'architectures':b.architecture(path),'host':{'system':'Fixture','machine':'Fixture'},'nativeVersion':{'status':'PASS','exitCode':0,'stdout':'terraphim-lsp 1.22.0-rc.4\n'},'commands':build_commands,'extra':extra} smoke=folder/'smoke'/target;smoke.mkdir(parents=True) (smoke/'input.md').write_text('Risk: delivery may slip.\n') (smoke/'brief.md').write_text('Risk: delivery may slip.\nFixture annotation: Concern\n') @@ -111,6 +116,18 @@ def test_real_subprocess_log_is_saved_and_hash_bound(self): b.command([sys.executable,'-c','raise SystemExit(4)'],lane,lane,'real-failure') self.assertTrue((lane/'logs/real-failure.log').is_file()) + def test_actual_producer_log_names_save_real_subprocess_output(self): + lane=self.root/'producer-names';lane.mkdir() + names=['universal', *[step+'-'+target for target in b.TARGETS for step in ('fetch','build')]] + for name in names: + with self.subTest(name=name): + record=b.command([sys.executable,'-c','print("actual producer-name output")'],lane,lane,name) + log=lane/'logs'/(name+'.log') + self.assertEqual(record['log'],name+'.log') + self.assertEqual(log.read_text(),'actual producer-name output\n') + self.assertEqual(record['sha256'],b.publisher.digest(log)) + self.assertEqual(record['exitCode'],0) + def test_cross_environment_omits_host_build_dir_without_changing_native(self): lane = self.root / 'lane' previous = os.environ.get('CARGO_BUILD_BUILD_DIR') @@ -163,6 +180,16 @@ def test_bundle_round_trip_preserves_six_raw_names(self): self.assertEqual({p.name for p in paths},set(b.publisher.RELEASE_FILES)) self.assertEqual(b.bundle(folder,q,self.expected),paths) + def test_log_names_must_match_the_exact_producer_target(self): + folder,q=self.fixture_bundle();target='x86_64-unknown-linux-musl';p=folder/(target+'.json') + record=json.loads(p.read_text()) + self.assertEqual([c['log'] for c in record['commands']],['fetch-'+target+'.log','build-'+target+'.log']) + for name in ['../build-'+target+'.log','build-aarch64-unknown-linux-musl.log','build.log','BUILD-'+target+'.log','build-'+target+'.log/other']: + with self.subTest(name=name): + record['commands'][-1]['log']=name;p.write_text(json.dumps(record)) + with self.assertRaisesRegex(ValueError,'Invalid build log name'): + b.bundle(folder,q,self.expected,write=True) + def test_mutated_asset_and_extra_file_refused(self): folder,q=self.fixture_bundle();b.bundle(folder,q,self.expected,write=True) (folder/b.RAW[0]).write_bytes(b'changed') @@ -170,7 +197,7 @@ def test_mutated_asset_and_extra_file_refused(self): def test_changed_build_log_and_wrong_target_refused(self): folder,q=self.fixture_bundle();target=b.RAW[0].removeprefix('terraphim-lsp-') - log=folder/'logs'/target/'build.log';log.write_text('changed') + log=folder/'logs'/target/('build-'+target+'.log');log.write_text('changed') with self.assertRaisesRegex(ValueError,'Build log'):b.bundle(folder,q,self.expected,write=True) def test_skipped_native_test_and_changed_source_refused(self): @@ -179,7 +206,7 @@ def test_skipped_native_test_and_changed_source_refused(self): def test_wrong_build_target_is_refused(self): folder,q=self.fixture_bundle();p=folder/'aarch64-unknown-linux-musl.json';record=json.loads(p.read_text()) - record['commands'][0]['argv'][-3]='x86_64-unknown-linux-musl';p.write_text(json.dumps(record)) + record['commands'][-1]['argv'][-3]='x86_64-unknown-linux-musl';p.write_text(json.dumps(record)) with self.assertRaisesRegex(ValueError,'Build target'):b.bundle(folder,q,self.expected,write=True) def test_extra_asset_is_refused(self):