Skip to content

Commit 5b9ffa9

Browse files
committed
chore: migrate opsm_ex/deps/proven checkpoint files from 6scm SCM to 6a2 A2ML
1 parent 2a49167 commit 5b9ffa9

25 files changed

Lines changed: 4253 additions & 4 deletions

opsm.toml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,34 @@ enabled = true
2929
timeout_ms = 5000
3030
instance_name = "opsm"
3131
base_iri = "https://verisim.db/opsm"
32+
33+
# ---------------------------------------------------------------------------
34+
# Package manifest (Hyperpolymath Forge Registry metadata)
35+
# The [package] section makes OPSM itself a first-class HFR package.
36+
# `opsm info opsm --registry hf` returns this metadata.
37+
# ---------------------------------------------------------------------------
38+
39+
[package]
40+
name = "odds-and-sods-package-manager"
41+
version = "2.0.0"
42+
description = "OPSM — federated universal package manager with 101+ registry adapters, trust pipeline, post-quantum crypto, and complete asdf replacement"
43+
license = "PMPL-1.0-or-later"
44+
authors = ["Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>"]
45+
keywords = ["package-manager", "federated", "trust", "post-quantum", "runtime-manager", "hyperpolymath"]
46+
homepage = "https://github.com/hyperpolymath/odds-and-sods-package-manager"
47+
repository = "https://github.com/hyperpolymath/odds-and-sods-package-manager"
48+
forth = "elixir"
49+
50+
[opsm]
51+
# Self-referential: OPSM is its own package manager
52+
trust_level = "hyperpolymath"
53+
registry = "hf"
54+
self_hosted = true
55+
56+
[runtime]
57+
# Pinned tool versions — managed by OPSM runtime extension (replaces .tool-versions)
58+
elixir = "1.16.0"
59+
erlang = "26.2.0"
60+
just = "1.46.0"
61+
deno = "2.6.10"
62+
zig = "0.14.0"
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
defmodule Opsm.Registries.Betlang do
4+
@moduledoc """
5+
Betlang package registry API client.
6+
7+
Betlang is a hard real-time systems language with static worst-case
8+
execution time (WCET) analysis, deterministic garbage collection, and
9+
formal timing proofs built into the type system.
10+
11+
Package manager: `bet` CLI (nextgen-languages/betlang/tools/bet-pkg, Rust)
12+
Manifest format: `betlang.toml` or `opsm.toml` ([package] forth = "betlang")
13+
Registry: https://packages.betlang.dev/api/v1 (planned — git fallback now)
14+
Language ID: betlang
15+
Source `:forth` atom: `:betlang`
16+
17+
Packages live under the hyperpolymath org until the central registry
18+
is deployed. Prefer `opsm.toml` with `forth = "betlang"`.
19+
"""
20+
21+
alias Opsm.Types.{ManifestFormat, ResolvedPackage}
22+
alias Opsm.Verified.Http, as: VerifiedHttp
23+
24+
@base_url "https://packages.betlang.dev/api/v1"
25+
@fallback_mode :git
26+
27+
@known_packages [
28+
%{
29+
name: "betlang-rt",
30+
url: "https://github.com/hyperpolymath/betlang",
31+
path: "runtime",
32+
description: "Betlang real-time runtime — deterministic scheduler, WCET guarantees, static allocation"
33+
},
34+
%{
35+
name: "betlang-hal",
36+
url: "https://github.com/hyperpolymath/betlang",
37+
path: "hal",
38+
description: "Hardware Abstraction Layer for Betlang — GPIO, timers, interrupts, DMA"
39+
},
40+
%{
41+
name: "betlang-rtos",
42+
url: "https://github.com/hyperpolymath/betlang",
43+
path: "rtos",
44+
description: "RTOS integration bindings for Betlang — FreeRTOS, Zephyr, ThreadX adapters"
45+
},
46+
%{
47+
name: "betlang-timing",
48+
url: "https://github.com/hyperpolymath/betlang",
49+
path: "timing",
50+
description: "Static timing analysis primitives — WCET types, deadline proofs, jitter bounds"
51+
},
52+
%{
53+
name: "betlang-std",
54+
url: "https://github.com/hyperpolymath/betlang",
55+
path: "stdlib",
56+
description: "Betlang standard library — deterministic collections, no-alloc I/O, safe concurrency"
57+
},
58+
%{
59+
name: "betlang-test",
60+
url: "https://github.com/hyperpolymath/betlang",
61+
path: "test",
62+
description: "Real-time test framework for Betlang — timing-accurate property testing"
63+
},
64+
%{
65+
name: "betlang-spark",
66+
url: "https://github.com/hyperpolymath/betlang",
67+
path: "spark-bridge",
68+
description: "SPARK/Ada interoperability layer for Betlang — formal proof bridging"
69+
}
70+
]
71+
72+
def fetch_package(name, version \\ "latest") do
73+
case @fallback_mode do
74+
:git -> fetch_from_git(name, version)
75+
:registry -> fetch_from_registry(name, version)
76+
end
77+
end
78+
79+
def search(query, opts \\ []) do
80+
case @fallback_mode do
81+
:git -> search_curated(query, opts)
82+
:registry -> search_registry(query, opts)
83+
end
84+
end
85+
86+
def exists?(name) do
87+
case @fallback_mode do
88+
:git -> curated_exists?(name)
89+
:registry -> registry_exists?(name)
90+
end
91+
end
92+
93+
def versions(name) do
94+
case @fallback_mode do
95+
:git -> git_versions(name)
96+
:registry -> registry_versions(name)
97+
end
98+
end
99+
100+
defp fetch_from_registry(name, version) do
101+
url = "#{@base_url}/packages/#{URI.encode(name)}"
102+
case VerifiedHttp.get_json(url, receive_timeout: 10_000) do
103+
{:ok, body} ->
104+
target = if version == "latest", do: body["latest_version"], else: version
105+
{:ok, parse_registry_package(body, target)}
106+
{:error, reason} -> {:error, reason}
107+
end
108+
end
109+
110+
defp search_registry(query, opts) do
111+
limit = Keyword.get(opts, :limit, 20)
112+
url = "#{@base_url}/packages?q=#{URI.encode(query)}&limit=#{limit}"
113+
case VerifiedHttp.get_json(url, receive_timeout: 10_000) do
114+
{:ok, body} when is_list(body) -> {:ok, Enum.map(body, &parse_search_result/1)}
115+
{:error, reason} -> {:error, reason}
116+
end
117+
end
118+
119+
defp registry_exists?(name) do
120+
case VerifiedHttp.get("#{@base_url}/packages/#{URI.encode(name)}", receive_timeout: 5_000) do
121+
{:ok, _} -> true
122+
_ -> false
123+
end
124+
end
125+
126+
defp registry_versions(name) do
127+
url = "#{@base_url}/packages/#{URI.encode(name)}/versions"
128+
case VerifiedHttp.get_json(url, receive_timeout: 10_000) do
129+
{:ok, body} when is_list(body) -> {:ok, Enum.map(body, & &1["version"])}
130+
{:error, reason} -> {:error, reason}
131+
end
132+
end
133+
134+
defp fetch_from_git(name, version) do
135+
case find_curated(name) do
136+
{:ok, pkg_info} -> fetch_git_manifest(pkg_info, version)
137+
:not_found -> try_github_locations(name, version)
138+
end
139+
end
140+
141+
defp search_curated(query, _opts) do
142+
q = String.downcase(query)
143+
results = Enum.filter(@known_packages, fn p ->
144+
String.contains?(String.downcase("#{p.name} #{p.description}"), q)
145+
end)
146+
{:ok, Enum.map(results, &curated_to_resolved(&1, "latest"))}
147+
end
148+
149+
defp curated_exists?(name), do: match?({:ok, _}, find_curated(name))
150+
151+
defp git_versions(name) do
152+
case find_curated(name) do
153+
{:ok, pkg_info} ->
154+
tags_url =
155+
pkg_info.url
156+
|> String.replace("https://github.com/", "https://api.github.com/repos/")
157+
|> Kernel.<>("/tags")
158+
case VerifiedHttp.get_json(tags_url, receive_timeout: 10_000) do
159+
{:ok, tags} when is_list(tags) ->
160+
versions = Enum.map(tags, & &1["name"]) |> Enum.reject(&is_nil/1)
161+
{:ok, if(versions == [], do: ["main"], else: versions)}
162+
_ -> {:ok, ["main"]}
163+
end
164+
:not_found -> {:error, :not_found}
165+
end
166+
end
167+
168+
defp try_github_locations(name, version) do
169+
urls = [
170+
"https://github.com/hyperpolymath/#{name}",
171+
"https://github.com/hyperpolymath/betlang-#{name}"
172+
]
173+
Enum.find_value(urls, {:error, :not_found}, fn url ->
174+
case fetch_git_manifest(%{name: name, url: url, description: nil}, version) do
175+
{:ok, pkg} -> {:ok, pkg}
176+
_ -> nil
177+
end
178+
end)
179+
end
180+
181+
defp fetch_git_manifest(pkg_info, version) do
182+
branch = if version in ["latest", "main"], do: "main", else: version
183+
path = Map.get(pkg_info, :path)
184+
base = pkg_info.url
185+
186+
for_manifest = fn manifest_name ->
187+
url = if path, do: "#{base}/raw/#{branch}/#{path}/#{manifest_name}",
188+
else: "#{base}/raw/#{branch}/#{manifest_name}"
189+
case VerifiedHttp.get(url, receive_timeout: 10_000) do
190+
{:ok, %{body: text}} -> {:ok, text}
191+
_ -> :not_found
192+
end
193+
end
194+
195+
with :not_found <- for_manifest.("opsm.toml"),
196+
:not_found <- for_manifest.("betlang.toml") do
197+
{:ok, curated_to_resolved(pkg_info, version)}
198+
else
199+
{:ok, toml_text} -> parse_betlang_toml(toml_text, pkg_info, version)
200+
end
201+
end
202+
203+
defp parse_betlang_toml(toml_text, pkg_info, version) do
204+
fields = extract_toml_section(toml_text, "package")
205+
pkg_name = fields["name"] || pkg_info.name
206+
207+
pkg = %ResolvedPackage{
208+
package: pkg_name,
209+
version: fields["version"] || version,
210+
forth: :betlang,
211+
registry_url: pkg_info.url,
212+
tarball_url: "#{pkg_info.url}/archive/#{version}.tar.gz",
213+
checksum: nil,
214+
checksum_algo: :sha256,
215+
manifest: %ManifestFormat{
216+
name: pkg_name,
217+
version: fields["version"] || version,
218+
description: fields["description"] || pkg_info[:description],
219+
license: fields["license"] || "PMPL-1.0-or-later",
220+
homepage: fields["homepage"] || pkg_info.url,
221+
repository: fields["repository"] || pkg_info.url,
222+
authors: parse_toml_array(fields["authors"]) || default_authors(),
223+
keywords: parse_toml_array(fields["keywords"]) || default_keywords(),
224+
dependencies: %{},
225+
dev_dependencies: %{},
226+
source_forth: :betlang,
227+
raw_manifest: fields
228+
},
229+
attestations: [],
230+
resolved_deps: []
231+
}
232+
{:ok, pkg}
233+
end
234+
235+
defp curated_to_resolved(pkg_info, version) do
236+
%ResolvedPackage{
237+
package: pkg_info.name,
238+
version: version,
239+
forth: :betlang,
240+
registry_url: pkg_info.url,
241+
tarball_url: "#{pkg_info.url}/archive/#{version}.tar.gz",
242+
checksum: nil,
243+
checksum_algo: :sha256,
244+
manifest: %ManifestFormat{
245+
name: pkg_info.name,
246+
version: version,
247+
description: pkg_info.description,
248+
license: "PMPL-1.0-or-later",
249+
homepage: pkg_info.url,
250+
repository: pkg_info.url,
251+
authors: default_authors(),
252+
keywords: default_keywords(),
253+
dependencies: %{},
254+
dev_dependencies: %{},
255+
source_forth: :betlang,
256+
raw_manifest: %{"registry" => "betlang-curated"}
257+
},
258+
attestations: [],
259+
resolved_deps: []
260+
}
261+
end
262+
263+
defp parse_registry_package(data, version) do
264+
%ResolvedPackage{
265+
package: data["name"],
266+
version: version,
267+
forth: :betlang,
268+
registry_url: @base_url,
269+
tarball_url: "#{@base_url}/packages/#{data["name"]}/#{version}/download",
270+
checksum: data["checksum"],
271+
checksum_algo: :sha256,
272+
manifest: %ManifestFormat{
273+
name: data["name"],
274+
version: version,
275+
description: data["description"],
276+
license: data["license"],
277+
homepage: data["homepage"],
278+
repository: data["repository"],
279+
authors: data["authors"] || [],
280+
keywords: data["keywords"] || [],
281+
dependencies: data["dependencies"] || %{},
282+
dev_dependencies: data["dev_dependencies"] || %{},
283+
source_forth: :betlang,
284+
raw_manifest: data
285+
},
286+
attestations: data["attestations"] || [],
287+
resolved_deps: []
288+
}
289+
end
290+
291+
defp parse_search_result(r) do
292+
%{name: r["name"], version: r["latest_version"],
293+
description: r["description"], downloads: r["downloads"] || 0}
294+
end
295+
296+
defp find_curated(name) do
297+
case Enum.find(@known_packages, &(&1.name == name)) do
298+
nil -> :not_found
299+
pkg -> {:ok, pkg}
300+
end
301+
end
302+
303+
defp extract_toml_section(toml_text, section_name) do
304+
{_inside, fields} =
305+
String.split(toml_text, "\n")
306+
|> Enum.reduce({false, %{}}, fn line, {inside, acc} ->
307+
t = String.trim(line)
308+
cond do
309+
t == "[#{section_name}]" -> {true, acc}
310+
String.starts_with?(t, "[") -> {false, acc}
311+
inside ->
312+
case String.split(t, " = ", parts: 2) do
313+
[k, v] -> {true, Map.put(acc, String.trim(k), String.trim(v, "\""))}
314+
_ -> {inside, acc}
315+
end
316+
true -> {inside, acc}
317+
end
318+
end)
319+
fields
320+
end
321+
322+
defp parse_toml_array(nil), do: nil
323+
defp parse_toml_array(str) when is_binary(str) do
324+
str
325+
|> String.trim("[") |> String.trim("]")
326+
|> String.split(",")
327+
|> Enum.map(&(&1 |> String.trim() |> String.trim("\"")))
328+
|> Enum.reject(&(&1 == ""))
329+
end
330+
331+
defp default_authors, do: ["Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>"]
332+
defp default_keywords, do: ["betlang", "real-time", "wcet", "embedded"]
333+
end

0 commit comments

Comments
 (0)