Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/assessments/release_manifest_proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Proposal: Generative Bootstrap Asset Manifest and Project-level Release Manifest

## 1. Background
Currently, bootstrap assets (such as `uv` and `opencode` installer) are managed via hardcoded environment variables in `scripts/init_system.sh` and `scripts/init_system_uv_release_manifest.sh`. This approach makes it difficult to:
- Synchronize versions across multiple scripts.
- Validate asset integrity automatically during CI/CD.
- Support air-gapped environments where a local mirror of assets is required.

## 2. Proposed Solution
Implement a generative manifest system that decouples asset definitions from deployment logic.

### 2.1 Release Manifest Structure
A JSON-based manifest (`release_manifest.json`) that explicitly defines all external dependencies required for bootstrapping.

```json
{
"version": "1.0",
"timestamp": "2026-03-15T03:23:01Z",
"assets": {
"uv": {
"version": "0.10.7",
"variants": {
"x86_64-unknown-linux-gnu": {
"name": "uv-x86_64-unknown-linux-gnu.tar.gz",
"url": "...",
"sha256": "..."
}
}
},
"opencode": {
"version": "1.2.5",
"url": "...",
"sha256": "..."
}
}
}
```

### 2.2 Generative Tooling
A Python script `scripts/gen_release_manifest.py` to automate the generation of this manifest. This script:
1. Fetches asset metadata from upstream sources.
2. Calculates SHA256 checksums.
3. Produces a machine-readable JSON file.

### 2.3 Consumption in Shell Scripts
Deployment scripts will be updated to consume the JSON manifest using `jq` (or a simple Python wrapper if `jq` is not available, though `jq` is standard in most ops environments).

Example usage in `init_system.sh`:
```bash
UV_VERSION=$(jq -r '.assets.uv.version' release_manifest.json)
UV_SHA256=$(jq -r '.assets.uv.variants["x86_64-unknown-linux-gnu"].sha256' release_manifest.json)
```

## 3. Implementation Plan
1. [x] Draft `scripts/gen_release_manifest.py`.
2. [x] Generate initial `release_manifest.json`.
3. [ ] Integrate manifest reading into `scripts/init_system.sh`.
4. [ ] Deprecate `scripts/init_system_uv_release_manifest.sh`.
5. [ ] Add a CI check to ensure `release_manifest.json` matches the generated output from the definitions in the script.

## 4. Architectural Reflection
- **Pros**: Single source of truth for dependencies; better security via verified checksums in a centralized place; easier to support private mirrors by rewriting URLs in the JSON.
- **Cons**: Adds a dependency on a JSON parser or a temporary Python invocation in shell scripts.
- **Decision**: The benefit of having a verifiable, project-level manifest outweighs the minor complexity of parsing JSON, especially as the project grows in scale.
36 changes: 36 additions & 0 deletions release_manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": "1.0",
"timestamp": "2026-03-15T03:23:01Z",
"assets": {
"uv": {
"version": "0.10.7",
"variants": {
"x86_64-unknown-linux-gnu": {
"name": "uv-x86_64-unknown-linux-gnu.tar.gz",
"url": "https://github.com/astral-sh/uv/releases/download/0.10.7/uv-x86_64-unknown-linux-gnu.tar.gz",
"sha256": "9ac6cee4e379a5abfca06e78a777b26b7ba1f81cb7935b97054d80d85ac00774"
},
"x86_64-unknown-linux-musl": {
"name": "uv-x86_64-unknown-linux-musl.tar.gz",
"url": "https://github.com/astral-sh/uv/releases/download/0.10.7/uv-x86_64-unknown-linux-musl.tar.gz",
"sha256": "992529add6024e67135b1c80617abd2eca7be2cf0b99b3911f923de815bd8dc1"
},
"aarch64-unknown-linux-gnu": {
"name": "uv-aarch64-unknown-linux-gnu.tar.gz",
"url": "https://github.com/astral-sh/uv/releases/download/0.10.7/uv-aarch64-unknown-linux-gnu.tar.gz",
"sha256": "20efc27d946860093650bcf26096a016b10fdaf03b13c33b75fbde02962beea9"
},
"aarch64-unknown-linux-musl": {
"name": "uv-aarch64-unknown-linux-musl.tar.gz",
"url": "https://github.com/astral-sh/uv/releases/download/0.10.7/uv-aarch64-unknown-linux-musl.tar.gz",
"sha256": "115291f9943531a3b63db3a2eabda8b74b8da4831551679382cb309c9debd9f7"
}
}
},
"opencode": {
"version": "1.2.5",
"url": "https://opencode.ai/install",
"sha256": "fc3c1b2123f49b6df545a7622e5127d21cd794b15134fc3b66e1ca49f7fb297e"
}
}
}
72 changes: 72 additions & 0 deletions scripts/gen_release_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import hashlib
import json
import os
import sys
import urllib.request
from typing import Dict, Any

# Current bootstrap versions (as found in scripts/init_system.sh)
MANIFEST_DEFAULTS = {
"uv": {
"version": "0.10.7",
"url_template": "https://github.com/astral-sh/uv/releases/download/{version}/{asset}",
"assets": {
"x86_64-unknown-linux-gnu": "uv-x86_64-unknown-linux-gnu.tar.gz",
"x86_64-unknown-linux-musl": "uv-x86_64-unknown-linux-musl.tar.gz",
"aarch64-unknown-linux-gnu": "uv-aarch64-unknown-linux-gnu.tar.gz",
"aarch64-unknown-linux-musl": "uv-aarch64-unknown-linux-musl.tar.gz",
}
},
"opencode": {
"version": "1.2.5",
"url": "https://opencode.ai/install",
}
}

def get_sha256(url: str) -> str:
print(f"Fetching {url} for hashing...", file=sys.stderr)
try:
with urllib.request.urlopen(url) as response:
data = response.read()
return hashlib.sha256(data).hexdigest()
except Exception as e:
print(f"Error fetching {url}: {e}", file=sys.stderr)
return ""

def generate_manifest() -> Dict[str, Any]:
manifest = {
"version": "1.0",
"timestamp": os.popen("date -u +'%Y-%m-%dT%H:%M:%SZ'").read().strip(),
"assets": {}
}

# UV Assets
uv_conf = MANIFEST_DEFAULTS["uv"]
uv_assets = {}
for arch_libc, asset_name in uv_conf["assets"].items():
url = uv_conf["url_template"].format(version=uv_conf["version"], asset=asset_name)
sha = get_sha256(url)
uv_assets[arch_libc] = {
"name": asset_name,
"url": url,
"sha256": sha
}
manifest["assets"]["uv"] = {
"version": uv_conf["version"],
"variants": uv_assets
}

# OpenCode Installer
oc_conf = MANIFEST_DEFAULTS["opencode"]
oc_sha = get_sha256(oc_conf["url"])
manifest["assets"]["opencode"] = {
"version": oc_conf["version"],
"url": oc_conf["url"],
"sha256": oc_sha
}

return manifest

if __name__ == "__main__":
manifest_data = generate_manifest()
print(json.dumps(manifest_data, indent=2))
Loading