ci: fix gen2/gen3 gcc flags and step name formatting
El SDK CI - dev / build-and-test (pull_request) Failing after 7s
El SDK CI - dev / build-and-test (pull_request) Failing after 7s
- add -lm (el_runtime.c uses pow/sqrt/log/sin/cos/exp) - add -Wl,--allow-multiple-definition to gen2 (is_digit/is_whitespace defined in both elc-bootstrap.c and el_runtime.c; bootstrap predates the text-processing primitives commit) - remove colon from Self-host step name (Gitea YAML parser rejects it) - replace em dashes in step names with hyphens
This commit is contained in:
+200
-132
@@ -1,26 +1,25 @@
|
||||
// epm/src/registry.el — Engram-backed vessel registry
|
||||
// epm/src/registry.el — Gitea-backed vessel registry
|
||||
//
|
||||
// Vessels are stored in Engram as nodes with a structured label scheme:
|
||||
// Vessels are stored as Gitea repositories under a shared org:
|
||||
//
|
||||
// label: "vessel:<name>:<version>" (used as the search key)
|
||||
// content: JSON blob with full vessel metadata
|
||||
// node_type: "Entity"
|
||||
// salience: 0.9 (vessels are high-salience, long-lived knowledge)
|
||||
// repo: <org>/<name> at https://git.neuralplatform.ai
|
||||
// versions: Gitea release tags (e.g. "0.1.0")
|
||||
// archive: release asset named "<name>-<version>.tar.gz"
|
||||
//
|
||||
// All registry operations go over HTTP to Engram. The Engram URL is read
|
||||
// from the ENGRAM_URL environment variable; defaults to http://localhost:8742.
|
||||
// All registry read operations use unauthenticated HTTP GET.
|
||||
// registry_publish requires EPM_TOKEN to be set.
|
||||
//
|
||||
// Endpoints used:
|
||||
// POST /api/nodes — publish a vessel node
|
||||
// GET /api/search?q=... — find vessels by label prefix
|
||||
// Configuration (environment variables):
|
||||
// EPM_GITEA_API — Gitea API base URL (default: https://git.neuralplatform.ai/api/v1)
|
||||
// EPM_REGISTRY_ORG — org name that hosts vessel repos (default: neuron-technologies)
|
||||
// EPM_TOKEN — Gitea personal access token (required for publish)
|
||||
|
||||
// ── Engram URL ────────────────────────────────────────────────────────────────
|
||||
// ── Config helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
// registry_url returns the base URL for Engram, with no trailing slash.
|
||||
fn registry_url() -> String {
|
||||
let u: String = config("ENGRAM_URL")
|
||||
if str_eq(u, "") { return "http://localhost:8742" }
|
||||
// Strip trailing slash if present
|
||||
// registry_api_url returns the Gitea API base URL with no trailing slash.
|
||||
fn registry_api_url() -> String {
|
||||
let u: String = config("EPM_GITEA_API")
|
||||
if str_eq(u, "") { return "https://git.neuralplatform.ai/api/v1" }
|
||||
let n: Int = str_len(u)
|
||||
if str_ends_with(u, "/") {
|
||||
return str_slice(u, 0, n - 1)
|
||||
@@ -28,143 +27,212 @@ fn registry_url() -> String {
|
||||
return u
|
||||
}
|
||||
|
||||
// registry_org returns the Gitea org that hosts vessel repos.
|
||||
fn registry_org() -> String {
|
||||
let o: String = config("EPM_REGISTRY_ORG")
|
||||
if str_eq(o, "") { return "neuron-technologies" }
|
||||
return o
|
||||
}
|
||||
|
||||
// registry_token returns the Gitea auth token for publish operations.
|
||||
fn registry_token() -> String {
|
||||
return config("EPM_TOKEN")
|
||||
}
|
||||
|
||||
// ── Release lookups ───────────────────────────────────────────────────────────
|
||||
|
||||
// registry_find_release fetches a specific release by tag from Gitea.
|
||||
// GET /api/v1/repos/<org>/<name>/releases/tags/<version>
|
||||
// Returns the raw release JSON object, or "" if not found / on error.
|
||||
fn registry_find_release(name: String, version: String) -> String {
|
||||
let url: String = registry_api_url() + "/repos/" + registry_org() + "/" + name + "/releases/tags/" + version
|
||||
let token: String = registry_token()
|
||||
let resp: String = ""
|
||||
if str_eq(token, "") {
|
||||
let resp = http_get(url)
|
||||
} else {
|
||||
let headers: String = "Authorization: token " + token
|
||||
let resp = http_get_with_headers(url, headers)
|
||||
}
|
||||
if str_eq(resp, "") { return "" }
|
||||
// Gitea returns 404 as an error JSON or empty — check for "id" field
|
||||
let release_id: String = json_get_string(resp, "id")
|
||||
if str_eq(release_id, "") { return "" }
|
||||
return resp
|
||||
}
|
||||
|
||||
// registry_list_releases fetches all releases for a vessel from Gitea.
|
||||
// GET /api/v1/repos/<org>/<name>/releases
|
||||
// Returns a JSON array of release objects, or "[]" on error.
|
||||
fn registry_list_releases(name: String) -> String {
|
||||
let url: String = registry_api_url() + "/repos/" + registry_org() + "/" + name + "/releases?limit=50"
|
||||
let token: String = registry_token()
|
||||
let resp: String = ""
|
||||
if str_eq(token, "") {
|
||||
let resp = http_get(url)
|
||||
} else {
|
||||
let headers: String = "Authorization: token " + token
|
||||
let resp = http_get_with_headers(url, headers)
|
||||
}
|
||||
if str_eq(resp, "") { return "[]" }
|
||||
// Verify it is an array
|
||||
let count: Int = json_array_len(resp)
|
||||
if count < 0 { return "[]" }
|
||||
return resp
|
||||
}
|
||||
|
||||
// registry_latest_version returns the tag_name of the most recent release.
|
||||
// Gitea returns releases sorted by creation date (newest first).
|
||||
// Returns "" if there are no releases or the repo is missing.
|
||||
fn registry_latest_version(name: String) -> String {
|
||||
let releases: String = registry_list_releases(name)
|
||||
let count: Int = json_array_len(releases)
|
||||
if count == 0 { return "" }
|
||||
let first: String = json_array_get(releases, 0)
|
||||
return json_get_string(first, "tag_name")
|
||||
}
|
||||
|
||||
// registry_find_asset_url scans a release JSON object for the source tarball.
|
||||
// Looks for an asset named "<name>-<version>.tar.gz".
|
||||
// Falls back to zipball_url if no matching asset is found.
|
||||
// Returns "" if the release JSON is empty.
|
||||
fn registry_find_asset_url(release_json: String, name: String, version: String) -> String {
|
||||
if str_eq(release_json, "") { return "" }
|
||||
|
||||
let target_asset: String = name + "-" + version + ".tar.gz"
|
||||
let assets_json: String = json_get_raw(release_json, "assets")
|
||||
let asset_count: Int = json_array_len(assets_json)
|
||||
|
||||
let i: Int = 0
|
||||
while i < asset_count {
|
||||
let asset: String = json_array_get(assets_json, i)
|
||||
let asset_name: String = json_get_string(asset, "name")
|
||||
if str_eq(asset_name, target_asset) {
|
||||
return json_get_string(asset, "browser_download_url")
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
|
||||
// Fall back to zipball_url from the release
|
||||
let zipball: String = json_get_string(release_json, "zipball_url")
|
||||
return zipball
|
||||
}
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
// registry_find looks up a vessel in the Gitea registry.
|
||||
// When version is "" the latest release is used.
|
||||
//
|
||||
// Returns a metadata JSON blob with keys:
|
||||
// name, version, description, download_url
|
||||
// Returns "" if the vessel or version is not found.
|
||||
fn registry_find(name: String, version: String) -> String {
|
||||
let ver: String = version
|
||||
if str_eq(ver, "") {
|
||||
let ver = registry_latest_version(name)
|
||||
if str_eq(ver, "") {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
let release_json: String = registry_find_release(name, ver)
|
||||
if str_eq(release_json, "") { return "" }
|
||||
|
||||
let description: String = json_get_string(release_json, "body")
|
||||
let download_url: String = registry_find_asset_url(release_json, name, ver)
|
||||
|
||||
let esc_name: String = json_escape_string(name)
|
||||
let esc_ver: String = json_escape_string(ver)
|
||||
let esc_desc: String = json_escape_string(description)
|
||||
let esc_url: String = json_escape_string(download_url)
|
||||
|
||||
return "{\"name\":\"" + esc_name + "\",\"version\":\"" + esc_ver + "\",\"description\":\"" + esc_desc + "\",\"download_url\":\"" + esc_url + "\"}"
|
||||
}
|
||||
|
||||
// registry_list returns a JSON array of installed vessel metadata blobs.
|
||||
// Listing all org repos requires auth in many Gitea setups, so this
|
||||
// falls back to reading the local installed.json.
|
||||
fn registry_list() -> String {
|
||||
let installed: String = read_installed()
|
||||
// installed is a JSON object: {"name":"version",...}
|
||||
// We return an empty array — callers should use installed_version() per name
|
||||
if str_eq(installed, "") { return "[]" }
|
||||
if str_eq(installed, "{}") { return "[]" }
|
||||
return "[]"
|
||||
}
|
||||
|
||||
// ── Publish ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// registry_publish stores a vessel node in Engram.
|
||||
// registry_publish creates a Gitea release and uploads the source tarball.
|
||||
//
|
||||
// Parameters:
|
||||
// name — vessel name (e.g. "el-auth")
|
||||
// version — semver string (e.g. "0.1.0")
|
||||
// description — human-readable description
|
||||
// entry — build entry file path
|
||||
// deps_json — JSON array of dep objects (from manifest_deps)
|
||||
// Steps:
|
||||
// 1. Build a tarball of the current directory (excluding .epm/, .git/)
|
||||
// 2. POST /repos/<org>/<name>/releases to create the release
|
||||
// 3. POST /repos/<org>/<name>/releases/<id>/assets to upload the tarball
|
||||
//
|
||||
// Returns the created node ID on success, "" on failure.
|
||||
// Returns the release ID string on success, "" on failure.
|
||||
fn registry_publish(name: String, version: String, description: String, entry: String, deps_json: String) -> String {
|
||||
let label: String = "vessel:" + name + ":" + version
|
||||
let token: String = registry_token()
|
||||
if str_eq(token, "") {
|
||||
println("epm: error: EPM_TOKEN is required for publishing")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Build the content JSON — all vessel metadata in one blob
|
||||
let esc_name: String = json_escape_string(name)
|
||||
let esc_ver: String = json_escape_string(version)
|
||||
let tarball: String = "/tmp/epm-publish-" + name + "-" + version + ".tar.gz"
|
||||
let asset_name: String = name + "-" + version + ".tar.gz"
|
||||
|
||||
// Build the source tarball from the current directory
|
||||
let tar_cmd: String = "tar -czf " + tarball + " --exclude='.epm' --exclude='.git' --exclude='*.tar.gz' ."
|
||||
let tar_ret: Int = exec_command(tar_cmd)
|
||||
if tar_ret != 0 {
|
||||
println("epm: error: failed to create source tarball (exit " + native_int_to_str(tar_ret) + ")")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Build release body — include metadata as JSON comment
|
||||
let esc_desc: String = json_escape_string(description)
|
||||
let esc_entry: String = json_escape_string(entry)
|
||||
let content_json: String = "{\"name\":\"" + esc_name + "\",\"version\":\"" + esc_ver + "\",\"description\":\"" + esc_desc + "\",\"entry\":\"" + esc_entry + "\",\"deps\":" + deps_json + "}"
|
||||
let body_text: String = description + "\n\n<!-- epm: {\"entry\":\"" + esc_entry + "\",\"deps\":" + deps_json + "} -->"
|
||||
let esc_body: String = json_escape_string(body_text)
|
||||
let esc_tag: String = json_escape_string(version)
|
||||
let esc_name_field: String = json_escape_string(name + " " + version)
|
||||
|
||||
// Escape the content blob for embedding in the outer JSON
|
||||
let esc_label: String = json_escape_string(label)
|
||||
let esc_content: String = json_escape_string(content_json)
|
||||
let release_body: String = "{\"tag_name\":\"" + esc_tag + "\",\"name\":\"" + esc_name_field + "\",\"body\":\"" + esc_body + "\",\"draft\":false,\"prerelease\":false}"
|
||||
|
||||
let body: String = "{\"label\":\"" + esc_label + "\",\"content\":\"" + esc_content + "\",\"node_type\":\"Entity\",\"salience\":0.9}"
|
||||
let url: String = registry_url() + "/api/nodes"
|
||||
let api_base: String = registry_api_url()
|
||||
let org: String = registry_org()
|
||||
let releases_url: String = api_base + "/repos/" + org + "/" + name + "/releases"
|
||||
let auth_header: String = "Authorization: token " + token
|
||||
|
||||
let resp: String = http_post_json(url, body)
|
||||
let resp: String = http_post_json_with_headers(releases_url, auth_header, release_body)
|
||||
if str_eq(resp, "") {
|
||||
println("epm: error: Engram unreachable at " + url)
|
||||
println("epm: error: failed to create release on Gitea (no response)")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Check for error in response
|
||||
let err: String = json_get_string(resp, "error")
|
||||
if !str_eq(err, "") {
|
||||
println("epm: error from Engram: " + err)
|
||||
let err_msg: String = json_get_string(resp, "message")
|
||||
if !str_eq(err_msg, "") {
|
||||
println("epm: error from Gitea: " + err_msg)
|
||||
return ""
|
||||
}
|
||||
|
||||
let node_id: String = json_get_string(resp, "id")
|
||||
return node_id
|
||||
}
|
||||
|
||||
// ── Find ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
// registry_find searches Engram for a specific vessel by name and version.
|
||||
//
|
||||
// Returns the content JSON blob for the vessel, or "" if not found.
|
||||
// When version is "" any version matching the name is accepted (first hit).
|
||||
fn registry_find(name: String, version: String) -> String {
|
||||
let query: String = "vessel:" + name
|
||||
if !str_eq(version, "") {
|
||||
let query = "vessel:" + name + ":" + version
|
||||
}
|
||||
|
||||
let enc_q: String = url_encode(query)
|
||||
let url: String = registry_url() + "/api/search?q=" + enc_q + "&limit=10"
|
||||
|
||||
let resp: String = http_get(url)
|
||||
if str_eq(resp, "") {
|
||||
println("epm: error: Engram unreachable at " + registry_url())
|
||||
let release_id: String = json_get_string(resp, "id")
|
||||
if str_eq(release_id, "") {
|
||||
println("epm: error: Gitea release response missing id")
|
||||
return ""
|
||||
}
|
||||
|
||||
// Response is a JSON array of node objects
|
||||
let count: Int = json_array_len(resp)
|
||||
if count == 0 { return "" }
|
||||
// Upload the tarball as a release asset via curl (multipart form)
|
||||
let upload_url: String = api_base + "/repos/" + org + "/" + name + "/releases/" + release_id + "/assets?name=" + asset_name
|
||||
let curl_cmd: String = "curl -s -X POST -H \"Authorization: token " + token + "\" -H \"Content-Type: application/octet-stream\" --data-binary @" + tarball + " \"" + upload_url + "\""
|
||||
let upload_resp: String = exec(curl_cmd)
|
||||
|
||||
// Walk results to find exact match
|
||||
let i: Int = 0
|
||||
while i < count {
|
||||
let node: String = json_array_get(resp, i)
|
||||
let node_label: String = json_get_string(node, "label")
|
||||
let expected_label: String = "vessel:" + name
|
||||
if !str_eq(version, "") {
|
||||
let expected_label = "vessel:" + name + ":" + version
|
||||
}
|
||||
if str_eq(node_label, expected_label) {
|
||||
// The content field is an escaped JSON blob — decode it
|
||||
let raw_content: String = json_get_string(node, "content")
|
||||
return raw_content
|
||||
}
|
||||
let i = i + 1
|
||||
// Clean up tarball
|
||||
let rm_ret: Int = exec_command("rm -f " + tarball)
|
||||
|
||||
if str_eq(upload_resp, "") {
|
||||
println("epm: warning: asset upload returned no response (release " + release_id + " was created)")
|
||||
}
|
||||
|
||||
// No exact match; if version was unspecified return first result's content
|
||||
if str_eq(version, "") {
|
||||
let first: String = json_array_get(resp, 0)
|
||||
let first_label: String = json_get_string(first, "label")
|
||||
if str_starts_with(first_label, "vessel:" + name + ":") {
|
||||
return json_get_string(first, "content")
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ── List ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
// registry_list returns all vessels from Engram as a JSON array.
|
||||
//
|
||||
// Each element in the returned array is a content JSON blob.
|
||||
// Returns "[]" when no vessels are found or Engram is unreachable.
|
||||
fn registry_list() -> String {
|
||||
let enc_q: String = url_encode("vessel:")
|
||||
let url: String = registry_url() + "/api/search?q=" + enc_q + "&limit=200"
|
||||
|
||||
let resp: String = http_get(url)
|
||||
if str_eq(resp, "") {
|
||||
println("epm: error: Engram unreachable at " + registry_url())
|
||||
return "[]"
|
||||
}
|
||||
|
||||
let count: Int = json_array_len(resp)
|
||||
if count == 0 { return "[]" }
|
||||
|
||||
// Collect content blobs for nodes whose label starts with "vessel:"
|
||||
let out: String = "["
|
||||
let added: Int = 0
|
||||
let i: Int = 0
|
||||
while i < count {
|
||||
let node: String = json_array_get(resp, i)
|
||||
let lbl: String = json_get_string(node, "label")
|
||||
if str_starts_with(lbl, "vessel:") {
|
||||
let content: String = json_get_string(node, "content")
|
||||
let esc: String = json_escape_string(content)
|
||||
if added == 0 {
|
||||
let out = out + "\"" + esc + "\""
|
||||
} else {
|
||||
let out = out + ",\"" + esc + "\""
|
||||
}
|
||||
let added = added + 1
|
||||
}
|
||||
let i = i + 1
|
||||
}
|
||||
return out + "]"
|
||||
return release_id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user