From 095e681664c647529d28f4a826e2b390113a40ac Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:58:55 -0700 Subject: [PATCH] Rinx by release tag only: pin v1.0.0; the generator enforces it (hagency-org/Rinx#37) [skip ci] - native-apps.json pins Rinx at tag v1.0.0 (commit 0879548b), the first Rinx release; the root pin is `tag = "v1.0.0"` and Cargo.lock records the commit. From 68afcf79 this brings Rinx #38 (feature parity), #39 (hosted pane layouts), #42 (Back at the root is the host's; sign-in in ~5 s on a fresh Android install) and #43 (1.0.0). - tools/native_apps.py: a git source may carry `tag` (a release tag, vX.Y.Z or vX.Y.Z-rc.N) with the commit it names in `rev`; apps in TAGGED_RELEASES (Rinx) must; --check also reads Cargo.lock and refuses a lock whose commit for the tag differs from the manifest's (a moved tag). - Tests for each rule. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 14 +++++----- Cargo.toml | 2 +- native-apps.json | 2 +- tools/native_apps.py | 55 +++++++++++++++++++++++++++++++++------ tools/test_native_apps.py | 30 +++++++++++++++++++-- 5 files changed, 84 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f0739c1..29afe791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,7 @@ dependencies = [ [[package]] name = "article-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=68afcf796d303aaf646eeb832d65c450a56c92b5#68afcf796d303aaf646eeb832d65c450a56c92b5" +source = "git+https://github.com/hagency-org/Rinx.git?tag=v1.0.0#0879548ba826c786eb6ad3f60dfba7bd342bd750" dependencies = [ "blake3", "image", @@ -373,7 +373,7 @@ dependencies = [ [[package]] name = "article-makepad" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=68afcf796d303aaf646eeb832d65c450a56c92b5#68afcf796d303aaf646eeb832d65c450a56c92b5" +source = "git+https://github.com/hagency-org/Rinx.git?tag=v1.0.0#0879548ba826c786eb6ad3f60dfba7bd342bd750" dependencies = [ "article-core", "libc", @@ -8887,8 +8887,8 @@ dependencies = [ [[package]] name = "rinx" -version = "1.0.0-beta.1" -source = "git+https://github.com/hagency-org/Rinx.git?rev=68afcf796d303aaf646eeb832d65c450a56c92b5#68afcf796d303aaf646eeb832d65c450a56c92b5" +version = "1.0.0" +source = "git+https://github.com/hagency-org/Rinx.git?tag=v1.0.0#0879548ba826c786eb6ad3f60dfba7bd342bd750" dependencies = [ "anyhow", "arboard", @@ -8962,7 +8962,7 @@ dependencies = [ [[package]] name = "rinx-miniapp-catalog" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=68afcf796d303aaf646eeb832d65c450a56c92b5#68afcf796d303aaf646eeb832d65c450a56c92b5" +source = "git+https://github.com/hagency-org/Rinx.git?tag=v1.0.0#0879548ba826c786eb6ad3f60dfba7bd342bd750" dependencies = [ "octosense-app-hub", "octosense-app-policy", @@ -8974,7 +8974,7 @@ dependencies = [ [[package]] name = "rinx-miniapp-core" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=68afcf796d303aaf646eeb832d65c450a56c92b5#68afcf796d303aaf646eeb832d65c450a56c92b5" +source = "git+https://github.com/hagency-org/Rinx.git?tag=v1.0.0#0879548ba826c786eb6ad3f60dfba7bd342bd750" dependencies = [ "serde", "serde_json", @@ -8983,7 +8983,7 @@ dependencies = [ [[package]] name = "rinx-system-apps" version = "0.1.0" -source = "git+https://github.com/hagency-org/Rinx.git?rev=68afcf796d303aaf646eeb832d65c450a56c92b5#68afcf796d303aaf646eeb832d65c450a56c92b5" +source = "git+https://github.com/hagency-org/Rinx.git?tag=v1.0.0#0879548ba826c786eb6ad3f60dfba7bd342bd750" dependencies = [ "octosense-app-policy", "serde", diff --git a/Cargo.toml b/Cargo.toml index e001b2a4..a3f51321 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ authors = ["octos-app contributors"] # Reference and AppCard. A git source shares its repository's revision with # the pins below; tools/native_apps.py writes this block and checks that. # BEGIN native-apps: pins (generated by tools/native_apps.py from native-apps.json; do not edit) -rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "68afcf796d303aaf646eeb832d65c450a56c92b5", default-features = false, features = ["octosense-module"] } +rinx = { git = "https://github.com/hagency-org/Rinx.git", tag = "v1.0.0", default-features = false, features = ["octosense-module"] } octosense-reference = { path = "apps/reference" } makepad-sheets = { git = "https://github.com/OctoSense-org/makepad.git", rev = "a5a3cf5b0dbd1b56c25defe6d98479f25843e770", default-features = false } makepad-terminal = { git = "https://github.com/OctoSense-org/makepad.git", rev = "a5a3cf5b0dbd1b56c25defe6d98479f25843e770" } diff --git a/native-apps.json b/native-apps.json index e9cdf315..70e3f20a 100644 --- a/native-apps.json +++ b/native-apps.json @@ -4,7 +4,7 @@ { "id": "rinx", "crate": "rinx", - "source": { "git": "https://github.com/hagency-org/Rinx.git", "rev": "68afcf796d303aaf646eeb832d65c450a56c92b5", "local": null }, + "source": { "git": "https://github.com/hagency-org/Rinx.git", "tag": "v1.0.0", "rev": "0879548ba826c786eb6ad3f60dfba7bd342bd750", "local": null }, "module": "rinx::module::RINX_MODULE", "bin": null, "default_features": false, diff --git a/tools/native_apps.py b/tools/native_apps.py index 1d206b73..0ea77e2d 100644 --- a/tools/native_apps.py +++ b/tools/native_apps.py @@ -28,7 +28,10 @@ It refuses `process` hosting on mobile or wasm targets, plain `process` on Linux (non-Vulkan Linux runs every app in-process: `process-if-vulkan`), `process` without a `bin`, and two revisions of one git repository in the -root pins. After a write, Cargo.lock is brought in step by cargo itself +root pins. Apps released by tag (Rinx, hagency-org/Rinx#37) must be pinned by +a release tag (`vX.Y.Z` or `vX.Y.Z-rc.N`) together with the commit it names: +the root pin says `tag = …`, and --check also reads Cargo.lock and refuses a +lock whose commit for that tag is not the manifest's `rev` (a moved tag). After a write, Cargo.lock is brought in step by cargo itself (`cargo update -p` for a pin that changed, then `cargo metadata`); --no-lock skips that. @@ -45,6 +48,11 @@ ROOT = Path(__file__).resolve().parents[1] MANIFEST = "native-apps.json" RUST_FILE = "crates/shell/src/native_apps.rs" +# Apps OctoSense takes only as tagged releases (hagency-org/Rinx#37): never a +# raw commit. main may take a release candidate; OctoSense's own releases take +# only a final release (checked by their release workflows, not here). +TAGGED_RELEASES = {"rinx"} +RELEASE_TAG = r"v\d+\.\d+\.\d+(-rc\.\d+)?" AGENTS_FILE = "crates/ai-host/src/native_agents.rs" PACKAGES = {"desktop": "desktop/Cargo.toml", "phone": "phone/Cargo.toml"} @@ -122,10 +130,14 @@ def validate(data): elif "path" in source: if set(source) != {"path"}: problems.append(f"{where}: a path source has only 'path'") - elif set(source) != {"git", "rev", "local"}: - problems.append(f"{where}: source is {{git, rev, local}} or {{path}}") + elif set(source) not in ({"git", "rev", "local"}, {"git", "tag", "rev", "local"}): + problems.append(f"{where}: source is {{git, rev, local}}, {{git, tag, rev, local}} or {{path}}") elif not re.fullmatch(r"[0-9a-f]{40}", str(source["rev"])): - problems.append(f"{where}: source.rev must be a full commit id") + problems.append(f"{where}: source.rev must be a full commit id (for a tag: the commit it names)") + elif "tag" in source and not re.fullmatch(RELEASE_TAG, str(source["tag"])): + problems.append(f"{where}: source.tag must be a release tag vX.Y.Z or vX.Y.Z-rc.N") + if isinstance(source, dict) and ident in TAGGED_RELEASES and "tag" not in source: + problems.append(f"{where}: is taken only as a tagged release (hagency-org/Rinx#37): pin source.tag, not a raw rev") if not modules_of(app) or not all(re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*(::[A-Za-z_][A-Za-z0-9_]*)+", m) for m in modules_of(app)): problems.append(f"{where}: module must be a Rust path (or a list of them)") if app["bin"] is not None and not (isinstance(app["bin"], str) and app["bin"]): @@ -402,7 +414,8 @@ def pin_line(app): if "path" in source: fields = [f"path = {s(source['path'])}"] else: - fields = [f"git = {s(source['git'])}", f"rev = {s(source['rev'])}"] + pin = f"tag = {s(source['tag'])}" if "tag" in source else f"rev = {s(source['rev'])}" + fields = [f"git = {s(source['git'])}", pin] if not app["default_features"]: fields.append("default-features = false") if app["crate_features"]: @@ -763,7 +776,7 @@ def check_one_revision(apps, text): problems = [] for app in apps: source = app["source"] - if "git" not in source: + if "git" not in source or "tag" in source: continue for crate, git, rev in workspace_pins(text): if same_git(git, source["git"]) and rev != source["rev"]: @@ -803,6 +816,29 @@ def read(rel): return out +def check_tagged_locks(root, apps): + """A tag-pinned app's Cargo.lock entry names the manifest's commit. + + Cargo records `git+?tag=#`; a tag moved after review + shows up here as a different commit.""" + lock = Path(root) / "Cargo.lock" + if not lock.is_file(): + return [] + text, problems = lock.read_text(), [] + for app in apps: + source = app["source"] + if "tag" not in source: + continue + pattern = re.compile(r'name = "%s"\nversion = "[^"]*"\nsource = "git\+([^"?#]+)\?tag=([^"#]+)#([0-9a-f]{40})"' % re.escape(app["crate"])) + found = [m for m in pattern.finditer(text) if same_git(m.group(1), source["git"])] + if not found: + problems.append(f"Cargo.lock has no {app['crate']} from {source['git']} at tag {source['tag']}; run python3 tools/native_apps.py") + for m in found: + if m.group(2) != source["tag"] or m.group(3) != source["rev"]: + problems.append(f"Cargo.lock: {app['crate']} {m.group(2)} is {m.group(3)[:12]}, native-apps.json says {source['tag']} is {source['rev'][:12]} (a moved tag?)") + return problems + + def changed_pins(old, new): """Crates whose pin in [workspace.dependencies] changed (not added).""" def pins(text): @@ -842,9 +878,12 @@ def main(argv=None, root=ROOT): if args.check: for rel in drifted: print(f"::error::{rel} is not what native-apps.json generates; run python3 tools/native_apps.py") - if not drifted: + lock_problems = check_tagged_locks(root, load(root)) + for problem in lock_problems: + print(f"::error::{problem}") + if not drifted and not lock_problems: note("every generated place matches native-apps.json") - return 1 if drifted else 0 + return 1 if drifted or lock_problems else 0 for rel in drifted: (Path(root) / rel).write_text(results[rel][1]) note(f"wrote {rel}") diff --git a/tools/test_native_apps.py b/tools/test_native_apps.py index c46f1912..fbbd1cf0 100644 --- a/tools/test_native_apps.py +++ b/tools/test_native_apps.py @@ -71,6 +71,32 @@ def assertRefused(self, pattern): class Validation(Fixture): + def test_rinx_is_taken_only_as_a_tagged_release(self): + source = self.app("rinx")["source"] + del source["tag"] + self.assertRefused(r"rinx.*tagged release") + + def test_refuses_a_tag_that_is_not_a_release(self): + self.app("rinx")["source"]["tag"] = "main" + self.assertRefused(r"release tag vX\.Y\.Z") + self.app("rinx")["source"]["tag"] = "v1.1.0-rc.2" + native_apps.validate(self.data) + + def test_a_tag_pin_names_the_tag_and_the_lock_its_commit(self): + rinx = self.app("rinx") + self.assertIn('tag = "%s"' % rinx["source"]["tag"], native_apps.pin_line(rinx)) + self.assertNotIn("rev =", native_apps.pin_line(rinx)) + shutil.copy(ROOT / "Cargo.lock", self.root / "Cargo.lock") + self.assertEqual(native_apps.check_tagged_locks(self.root, native_apps.load(self.root)), []) + + def test_a_moved_tag_fails_the_check(self): + shutil.copy(ROOT / "Cargo.lock", self.root / "Cargo.lock") + self.app("rinx")["source"]["rev"] = "f" * 40 + self.save() + problems = native_apps.check_tagged_locks(self.root, native_apps.load(self.root)) + self.assertTrue(problems and "moved tag" in problems[0], problems) + self.assertEqual(self.run_main("--check"), 1) + def test_refuses_process_on_mobile_and_wasm(self): for target in ("android", "ios", "ohos", "wasm"): self.app("terminal")["hosting"][target] = "process" @@ -274,13 +300,13 @@ def test_missing_markers_are_an_error(self): native_apps.generate(self.root) def test_a_changed_pin_is_updated_through_cargo(self): - self.app("rinx")["source"]["rev"] = "1" * 40 + self.app("rinx")["source"].update(tag="v1.0.1", rev="1" * 40) self.save() with patch.object(native_apps.subprocess, "run") as run: self.assertEqual(self.run_main(), 0) commands = [call.args[0] for call in run.call_args_list] self.assertEqual(commands, [["cargo", "update", "-p", "rinx"], ["cargo", "metadata", "--format-version", "1"]]) - self.assertIn('rinx = { git = "https://github.com/hagency-org/Rinx.git", rev = "' + "1" * 40 + '"', + self.assertIn('rinx = { git = "https://github.com/hagency-org/Rinx.git", tag = "v1.0.1"', (self.root / "Cargo.toml").read_text()) def test_nothing_changed_runs_no_cargo(self):