Skip to content

Commit 0fe90e3

Browse files
committed
Finalize CellScript 0.24 release hardening
1 parent 13bcec8 commit 0fe90e3

11 files changed

Lines changed: 269 additions & 103 deletions

File tree

BRANCHES.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,12 @@ discussions. Do not use that historical baseline to describe the current
88

99
## nightly-0.24
1010

11-
`nightly-0.24` is the active release-candidate line for independently verified
11+
`nightly-0.24` is the closed maintenance line for independently verified
1212
artifacts and executable package evidence. It builds on the closed 0.23
13-
Edition 2026 and native-tooling boundary. The stable release boundary will be
14-
the exact `v0.24.0` tag only after the compiler, checker, Registry worker,
15-
executable tests, source maps, docs, and the `dev`, `ci`, `backend`, and
16-
`release` gates agree on the final source. Until then, passing candidate gates
17-
is evidence, not a stable-release claim. External Myelin, Fiber, and RGB++
18-
claims remain separately evidence gated as described in the 0.24 release
19-
notes.
13+
Edition 2026 and native-tooling boundary. The stable release boundary is the
14+
exact `v0.24.0` tag; later commits on the branch are not implicitly part of
15+
that release. External Myelin, Fiber, and RGB++ claims remain separately
16+
evidence gated as described in the 0.24 release notes.
2017

2118
## nightly-0.23
2219

@@ -39,18 +36,19 @@ nightly branch name.
3936
comparisons and an exact nightly branch for development-scope comparisons;
4037
do not infer release evidence from `main` alone.
4138

42-
## v0.24.0 (reserved)
39+
## v0.24.0
4340

44-
`v0.24.0` is reserved for the final 0.24 source and has not been issued. Do not
45-
use the name as a stable comparison until the final tag exists. The eventual
46-
release will not by itself promote the separately pending Myelin, Fiber, or
47-
RGB++ external evidence boundaries.
41+
`v0.24.0` is the current stable release for the verified-artifact checker,
42+
executable package scenarios, lock-authoritative package graph, and LS-IDL
43+
Registry path. Use the exact tag ref `refs/tags/v0.24.0` for stable
44+
comparisons. The release does not promote the separately pending Myelin,
45+
Fiber, or RGB++ external evidence boundaries.
4846

4947
## v0.23.0
5048

51-
`v0.23.0` remains the current stable baseline for Edition 2026, the Registry,
52-
and native release tooling. Use the exact tag ref `refs/tags/v0.23.0` for
53-
stable comparisons while 0.24 remains a release candidate.
49+
`v0.23.0` is the historical stable baseline for Edition 2026, the Registry,
50+
and native release tooling. Use the exact tag ref `refs/tags/v0.23.0` when
51+
reproducing that release.
5452

5553
## v0.22.0
5654

CHANGELOG.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## 0.24.0 - Unreleased
3+
## 0.24.0 - 2026-08-22
44

55
- Align the complete 0.24 release identity across every workspace and verifier
66
crate, the independent checker dependency, lockfiles, Registry Type Script,
@@ -16,6 +16,12 @@
1616
collection, expression, frame, runtime, schema, and Cell-operation modules;
1717
remove crate-wide Clippy exemptions; and replace long positional helper
1818
signatures with named context records.
19+
- Harden the final wide-integer boundary: resolve dynamic Molecule-backed
20+
`u128` fields before loading limbs, preserve the left operand across a
21+
second dynamic load, and make `u128 +/- u64` overflow and underflow fail
22+
closed with runtime error 49. Add exact CKB-VM regression vectors, remove
23+
zero-divisor paths from the NFT and vesting examples, and reject
24+
non-canonical SemVer at Registry admission.
1925
- Remove the CKB adapter's deprecated, permanently fail-closed automatic
2026
deployment methods. Callers must build a verified unsigned deployment
2127
transaction and hand signing to an external wallet.
@@ -33,13 +39,12 @@
3339
origins, chain selection, sandbox expiry, no-index policy, and storage remain
3440
isolated.
3541
- Preserve the corrected website release lineage that removed stale 0.22
36-
metadata. While 0.24 remains untagged, keep the homepage on the published
37-
`v0.23.0` release and prepare the Playground with the 0.24 candidate
38-
compiler. The canonical WASM bundle uses asset identity
39-
`20260819-v0.24.0-19ce8898` and SHA-256
42+
metadata. Publish the homepage as `v0.24.0` while keeping the Playground on
43+
the matching 0.24 compiler identity. The canonical WASM bundle uses asset
44+
identity `20260819-v0.24.0-19ce8898` and SHA-256
4045
`19ce8898e8161f100edebf6f982d856f3e59bfac31572642b53f2e01c70a1a17`;
4146
distribution checks bind the current stable release URL and displayed tag
42-
separately from the candidate compiler version, asset identity, and digest.
47+
separately from the compiler version, asset identity, and digest.
4348
Remove inherited 0.25-only package-interface, typed-semantics, and future-
4449
syntax presentation fields from the 0.24 website branch while retaining the
4550
0.24 LS-IDL surface. Publish the exact 0.24 and 0.25 website gitlinks on

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</p>
55

66
[![CellScript CI](https://github.com/CellScript-Labs/CellScript/actions/workflows/ci.yml/badge.svg)](https://github.com/CellScript-Labs/CellScript/actions/workflows/ci.yml)
7-
[![Release: v0.23.0](https://img.shields.io/badge/release-v0.23.0-2f6f4e.svg)](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.23.0)
7+
[![Release: v0.24.0](https://img.shields.io/badge/release-v0.24.0-2f6f4e.svg)](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.24.0)
88
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT)
99
[![Rust 1.97.1](https://img.shields.io/badge/rust-1.97.1-orange.svg)](Cargo.toml)
1010
[![Targets: CKB](https://img.shields.io/badge/targets-CKB-2f6f4e.svg)](#target-profiles)
@@ -20,9 +20,9 @@ artifacts, together with typed metadata for auditing, policy checks, schema
2020
binding, and scheduler-aware execution.
2121

2222
The current stable release is
23-
[CellScript v0.23.0](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.23.0).
23+
[CellScript v0.24.0](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.24.0).
2424
The [0.24 release notes](docs/releases/CELLSCRIPT_0_24_RELEASE_NOTES.md)
25-
describe the active release candidate and are not yet a stable-release claim.
25+
describe its verified-artifact, executable-test, and integration boundaries.
2626
The [0.24 roadmap](roadmap/CELLSCRIPT_0_24_ROADMAP.md) records its independently
2727
checked artifact, executable-test, and explicitly deferred ecosystem
2828
boundaries.
@@ -119,15 +119,15 @@ curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scr
119119
Or pin a specific version:
120120

121121
```bash
122-
CELLSCRIPT_VERSION=0.23.0 curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scripts/install.sh | sh
122+
CELLSCRIPT_VERSION=0.24.0 curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scripts/install.sh | sh
123123
```
124124

125125
The release page publishes `SHA256SUMS` alongside all four platform archives.
126126

127127
Build the exact published source instead:
128128

129129
```bash
130-
git clone --branch v0.23.0 --depth 1 https://github.com/CellScript-Labs/CellScript.git
130+
git clone --branch v0.24.0 --depth 1 https://github.com/CellScript-Labs/CellScript.git
131131
cd CellScript
132132
cargo install --locked --path .
133133
```

docs/releases/CELLSCRIPT_0_24_RELEASE_NOTES.md

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# CellScript 0.24 Release Notes
22

3-
**Status**: stable-release candidate on `nightly-0.24`; no final tag or release
4-
date has been issued. Candidate `dev`, `ci`, `backend`, and `release` evidence
5-
is recorded in the validation section. The refreshed iCKB
3+
**Status**: stable release at `refs/tags/v0.24.0`. Final `dev`, `ci`, `backend`,
4+
`release-quick`, and `release` evidence is recorded in the validation section.
5+
The refreshed iCKB
66
evidence submodule commit `0e18ccd97bd75cac7de9211dc8d344c0bc08942f` is
77
published and bound by the parent gitlink. External ecosystem claims remain
88
limited to the explicit integration status below.
99

10-
**Release date**: TBD
10+
**Release date**: 2026-08-22
1111

1212
**Source edition**: 2026
1313

@@ -171,23 +171,23 @@ not claim to emit or execute a production ELF.
171171

172172
## Website Release Identity
173173

174-
The 0.24 website carries forward the corrected release boundary. While the
175-
candidate remains untagged, its homepage continues to name `v0.23.0` as the
176-
current stable release, while its Playground worker, compiler sample, and
177-
distribution checks prepare the 0.24 compiler identity. Its 0.24 release
178-
branch removes the forward-looking 0.25-only package-interface,
179-
typed-semantics, and syntax-highlighting presentation fields; those remain
180-
outside the 0.24 release. The canonical Playground asset is
174+
The 0.24 website carries forward the corrected release boundary. Its homepage
175+
names `v0.24.0` as the current stable release, while its Playground worker,
176+
compiler sample, and distribution checks use the matching 0.24 compiler
177+
identity. Its 0.24 release branch removes the forward-looking 0.25-only
178+
package-interface, typed-semantics, and syntax-highlighting presentation
179+
fields; those remain outside the 0.24 release. The canonical Playground asset
180+
is
181181
`20260819-v0.24.0-19ce8898`; its WASM SHA-256 is
182182
`19ce8898e8161f100edebf6f982d856f3e59bfac31572642b53f2e01c70a1a17`.
183183
The raw module is 1,485,936 bytes and 567,048 bytes under the gate's gzip
184184
measurement, below the 600 KiB budget.
185185

186186
The Node 22 website build validates both production and Pudge Testnet outputs,
187-
the six-route byte-identical asset parity boundary, the current `v0.23.0`
188-
release URL and tag, the candidate compiler asset identity, and the exact WASM
189-
digest. The parent repository pins website commit
190-
`dd4e895bd0a68b6fa63bc59c6377b7db0864cd76` on the published
187+
the six-route byte-identical asset parity boundary, the current `v0.24.0`
188+
release URL and tag, the compiler asset identity, and the exact WASM digest.
189+
The parent repository pins website commit
190+
`9849c0cb051439901bd3d9c01bd6ba58e8e40751` on the published
191191
`codex/nightly-0.24-release` branch.
192192

193193
The 0.25-only Playground verification surface remains on the separate
@@ -227,6 +227,14 @@ grid, clearer evidence hierarchy, refined substrate material, and the restored
227227
homepage brand animation. These presentation changes do not alter Registry
228228
evidence semantics or the mainnet/testnet isolation boundary.
229229

230+
Final release hardening resolves dynamic Molecule-backed `u128` pointers once
231+
before limb loads and spills the first wide operand while loading the second,
232+
so schema validation cannot overwrite live arithmetic state. Mixed
233+
`u128 +/- u64` operations now reject overflow and underflow with runtime error
234+
49. Exact CKB-VM vectors cover both boundaries. Registry admission now rejects
235+
non-canonical SemVer forms such as leading-zero core or numeric prerelease
236+
identifiers.
237+
230238
## Integration Status
231239

232240
- The CellScript side of the Myelin 0.24 handoff is versioned and tested. The
@@ -353,10 +361,9 @@ or conversion of executable/copy artifacts into source dependencies.
353361

354362
## Validation
355363

356-
The release-candidate snapshot at commit
357-
`b90c4b562cceb95059bf410c617c920fa2483570` passed all five canonical gates on
358-
the validation date 2026-08-20. This validation date is not the release date.
359-
The environment used Rust `1.97.1`, Node `22.23.2`, CKB revision
364+
The exact source identified by `refs/tags/v0.24.0` passed all five canonical
365+
gates on the release date 2026-08-22. The environment used Rust `1.97.1`, Node
366+
`22.23.2`, CKB revision
360367
`f7fa4436737756f97a24e254f22c13a36316ecea`, CKB SDK `v5.1.0`, the
361368
`riscv64imac-unknown-none-elf` target, and the pinned Docker base
362369
`rust:1.97.1-slim-bookworm@sha256:99e09cb2284e2ddbb73a995deee3e91783fd04d177602ccf6eab326d778ee777`:
@@ -393,10 +400,7 @@ The canonical website build produced the 0.24 WASM identity recorded above and
393400
passed the 600 KiB gzip budget. Gates establish source and artifact evidence;
394401
they do not publish either crates.io package or deploy the Registry Type
395402
Script. Publication and deployment remain explicit release-operator actions
396-
and must preserve the validated source and artifact identities. Because the
397-
candidate remains open to change, the final operator must rerun the required
398-
gates on the exact source that receives `v0.24.0`; the tag and release date
399-
remain unset until then.
403+
and must preserve the validated source and artifact identities.
400404

401405
## Detailed References
402406

services/registry-api/src/domain.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,13 @@ export function validatePackageIdent(value: string, field: string): string {
378378

379379
export function validateVersion(value: string): string {
380380
const trimmed = value.trim();
381-
if (!/^[0-9]+[.][0-9]+[.][0-9]+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed)) {
382-
throw new ApiError(400, "invalid_version", "version must be semver-like");
381+
const match = /^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:[.][0-9A-Za-z-]+)*))?(?:[+]([0-9A-Za-z-]+(?:[.][0-9A-Za-z-]+)*))?$/.exec(trimmed);
382+
if (!match) {
383+
throw new ApiError(400, "invalid_version", "version must be valid SemVer");
384+
}
385+
const prerelease = match[4]?.split(".") ?? [];
386+
if (prerelease.some((identifier) => /^[0-9]+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"))) {
387+
throw new ApiError(400, "invalid_version", "numeric SemVer prerelease identifiers must not contain leading zeroes");
383388
}
384389
return trimmed;
385390
}

services/registry-api/test/registry-api.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
sha256Hex,
3434
validatePublishPayload,
3535
validateArtifactDescriptor,
36+
validateVersion,
3637
type CapabilityAuthorisationPayload,
3738
type CapabilityRevocationPayload,
3839
type AvailabilityPayload,
@@ -95,6 +96,20 @@ describe("capability scopes", () => {
9596
});
9697
});
9798

99+
describe("SemVer admission", () => {
100+
it("accepts canonical release, prerelease, and build metadata forms", () => {
101+
expect(validateVersion("0.24.0")).toBe("0.24.0");
102+
expect(validateVersion("1.2.3-rc.1+build.7")).toBe("1.2.3-rc.1+build.7");
103+
});
104+
105+
it.each(["01.2.3", "1.02.3", "1.2.03", "1.2.3-01", "1.2.3-rc..1", "1.2.3+"])(
106+
"rejects non-canonical version %s",
107+
(version) => {
108+
expect(() => validateVersion(version)).toThrow(ApiError);
109+
},
110+
);
111+
});
112+
98113
function bytesHex(value: Uint8Array): string {
99114
return `0x${[...value].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
100115
}

src/codegen/expr.rs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,7 @@ impl CodeGenerator {
190190
self.emit_fail(CellScriptRuntimeError::FixedByteComparisonUnresolved);
191191
return;
192192
};
193-
if !self.emit_u128_operand_limbs("t0", "t1", "t6", "t4", left, "u128 arithmetic left") {
194-
return;
195-
}
196-
if !self.emit_u128_operand_limbs("t2", "t3", "t6", "t5", right, "u128 arithmetic right") {
193+
if !self.emit_u128_binary_operand_limbs(left, right, "u128 arithmetic") {
197194
return;
198195
}
199196
let ok_label = self.fresh_label("u128_arithmetic_ok");
@@ -237,10 +234,7 @@ impl CodeGenerator {
237234
}
238235

239236
fn emit_u128_compare(&mut self, dest: &IrVar, op: BinaryOp, left: &IrOperand, right: &IrOperand) {
240-
if !self.emit_u128_operand_limbs("t0", "t1", "t6", "t4", left, "u128 compare left") {
241-
return;
242-
}
243-
if !self.emit_u128_operand_limbs("t2", "t3", "t6", "t5", right, "u128 compare right") {
237+
if !self.emit_u128_binary_operand_limbs(left, right, "u128 compare") {
244238
return;
245239
}
246240
self.emit("# cellscript abi: u128 compare high limb first");
@@ -292,10 +286,7 @@ impl CodeGenerator {
292286
self.emit_fail(CellScriptRuntimeError::FixedByteComparisonUnresolved);
293287
return;
294288
};
295-
if !self.emit_u128_operand_limbs("t0", "t1", "t6", "t4", left, "u128 multiplication left") {
296-
return;
297-
}
298-
if !self.emit_u128_operand_limbs("t2", "t3", "t6", "t5", right, "u128 multiplication right") {
289+
if !self.emit_u128_binary_operand_limbs(left, right, "u128 multiplication") {
299290
return;
300291
}
301292
self.emit("# cellscript abi: checked u128 multiplication");
@@ -347,10 +338,7 @@ impl CodeGenerator {
347338
self.emit_fail(CellScriptRuntimeError::FixedByteComparisonUnresolved);
348339
return;
349340
};
350-
if !self.emit_u128_operand_limbs("t0", "t1", "t6", "t4", left, "u128 division numerator") {
351-
return;
352-
}
353-
if !self.emit_u128_operand_limbs("t2", "t3", "t6", "t5", right, "u128 division denominator") {
341+
if !self.emit_u128_binary_operand_limbs(left, right, "u128 division") {
354342
return;
355343
}
356344
self.emit("# cellscript abi: checked u128 division by restoring long division");

src/codegen/frame.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,17 +1063,12 @@ impl CodeGenerator {
10631063
self.emit_store_u128_pointer_for_var(dest.id, dest_offset);
10641064
return true;
10651065
}
1066-
let Some(source) = self.expected_u128_source(src) else {
1067-
self.emit("# cellscript abi: u128 source is not addressable; fail closed");
1068-
self.emit_fail(CellScriptRuntimeError::FixedByteComparisonUnresolved);
1069-
return true;
1070-
};
1071-
self.emit_prepare_fixed_byte_source(&source, 16, "u128 materialize");
10721066
self.emit(format!("# cellscript abi: materialize u128 operand into var{}", dest.id));
1073-
for byte_index in 0..16 {
1074-
self.emit_fixed_byte_source_byte_to("t0", "t4", &source, byte_index);
1075-
self.emit_store_byte_to_stack_offset("t0", dest_offset + byte_index);
1067+
if !self.emit_u128_operand_limbs("t0", "t1", "t6", "t4", src, "u128 materialize") {
1068+
return true;
10761069
}
1070+
self.emit_stack_store("t0", dest_offset);
1071+
self.emit_stack_store("t1", dest_offset + 8);
10771072
self.emit_store_u128_pointer_for_var(dest.id, dest_offset);
10781073
true
10791074
}
@@ -1111,8 +1106,37 @@ impl CodeGenerator {
11111106
return false;
11121107
};
11131108
self.emit_prepare_fixed_byte_source(&source, 16, context);
1114-
self.emit_u64_le_from_fixed_byte_source(low_reg, scratch_reg, base_reg, &source, 0);
1115-
self.emit_u64_le_from_fixed_byte_source(high_reg, scratch_reg, base_reg, &source, 8);
1109+
if self.emit_fixed_byte_source_pointer_to(base_reg, &source) {
1110+
// Resolve schema-backed pointers before either accumulator is
1111+
// live. Dynamic Molecule bounds checks use t0..t5 internally, so
1112+
// resolving the pointer for every byte would overwrite the limb
1113+
// being assembled.
1114+
self.emit_unaligned_scalar_load(base_reg, low_reg, scratch_reg, 0, 8);
1115+
self.emit_unaligned_scalar_load(base_reg, high_reg, scratch_reg, 8, 8);
1116+
} else {
1117+
// Constants intentionally have no addressable storage.
1118+
self.emit_u64_le_from_fixed_byte_source(low_reg, scratch_reg, base_reg, &source, 0);
1119+
self.emit_u64_le_from_fixed_byte_source(high_reg, scratch_reg, base_reg, &source, 8);
1120+
}
1121+
true
1122+
}
1123+
1124+
pub(super) fn emit_u128_binary_operand_limbs(&mut self, left: &IrOperand, right: &IrOperand, context: &str) -> bool {
1125+
if !self.emit_u128_operand_limbs("t0", "t1", "t6", "t4", left, &format!("{} left", context)) {
1126+
return false;
1127+
}
1128+
let left_low_offset = self.runtime_expr_temp_offset(0);
1129+
let left_high_offset = self.runtime_expr_temp_offset(1);
1130+
self.emit_stack_store("t0", left_low_offset);
1131+
self.emit_stack_store("t1", left_high_offset);
1132+
if !self.emit_u128_operand_limbs("t2", "t3", "t6", "t5", right, &format!("{} right", context)) {
1133+
return false;
1134+
}
1135+
// Loading a dynamic schema field runs Molecule validation that uses
1136+
// t0/t1. Restore the left limbs only after the right operand is fully
1137+
// materialized.
1138+
self.emit_stack_load("t0", left_low_offset);
1139+
self.emit_stack_load("t1", left_high_offset);
11161140
true
11171141
}
11181142

0 commit comments

Comments
 (0)