silicon-cli 1.0.20__tar.gz → 1.0.22__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/.github/workflows/publish-runtime.yml +0 -5
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/MANIFEST.in +1 -1
- {silicon_cli-1.0.20/silicon_cli.egg-info → silicon_cli-1.0.22}/PKG-INFO +40 -60
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/README.md +39 -58
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/docker/runtime/Dockerfile +3 -3
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/pyproject.toml +1 -5
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/__init__.py +1 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/cli.py +0 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/config.py +16 -10
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/docker_runtime.py +24 -23
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/package_manager.py +36 -25
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/runtime_contract.py +4 -4
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/stemcell.py +9 -16
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/sync.py +1 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/update.py +12 -22
- silicon_cli-1.0.22/silicon_cli/updater/channel.py +59 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/engine.py +11 -30
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/generation.py +97 -70
- silicon_cli-1.0.22/silicon_cli/updater/release.py +1157 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/retention.py +1 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/snapshot_adapter.py +34 -61
- {silicon_cli-1.0.20 → silicon_cli-1.0.22/silicon_cli.egg-info}/PKG-INFO +40 -60
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/SOURCES.txt +1 -3
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/requires.txt +0 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_docker_runtime.py +3 -3
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_docker_update_hooks.py +16 -2
- silicon_cli-1.0.22/tests/test_git_release_source.py +440 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_package_manager.py +19 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_runtime_contract.py +2 -2
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_snapshot_adapter.py +1 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_stemcell_hydration.py +5 -3
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_transactional_updater.py +246 -1
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_update.py +63 -9
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/uv.lock +1 -4
- silicon_cli-1.0.20/silicon_cli/release_trust_anchors.json +0 -6
- silicon_cli-1.0.20/silicon_cli/updater/channel.py +0 -68
- silicon_cli-1.0.20/silicon_cli/updater/release.py +0 -772
- silicon_cli-1.0.20/silicon_cli/updater/signatures.py +0 -226
- silicon_cli-1.0.20/tests/test_signed_release_source.py +0 -373
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/LICENSE +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/docker/runtime/Dockerfile.local +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/docker/runtime/runtime-entrypoint.sh +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/setup.cfg +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/backup_runtime.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/glassagent.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/host_lock.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/http_transport.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/interface_cli.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/process.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/pull_transaction.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/registry.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/ui.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/__init__.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/cache.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/checkpoint.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/fleet.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/io.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/journal.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/lock.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/maintenance.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/overlay.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/planner.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/policy.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/dependency_links.txt +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/entry_points.txt +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/top_level.txt +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_backup_runtime.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_browser_profile.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_extend_installation.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_fleet_update_journal.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_glassagent_process.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_http_transport.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_process_health.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_pull_args.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_pull_transaction.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_sync_credentials_backup.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_update_maintenance.py +0 -0
- {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_update_overlay_retention.py +0 -0
|
@@ -52,11 +52,6 @@ jobs:
|
|
|
52
52
|
username: ${{ github.actor }}
|
|
53
53
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
54
54
|
|
|
55
|
-
- name: Require a packaged production release trust anchor
|
|
56
|
-
run: |
|
|
57
|
-
python -m pip install --disable-pip-version-check .
|
|
58
|
-
python -m silicon_cli.updater.signatures --check-packaged
|
|
59
|
-
|
|
60
55
|
- name: Build and push runtime image
|
|
61
56
|
id: build
|
|
62
57
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
|
@@ -4,7 +4,7 @@ include pyproject.toml
|
|
|
4
4
|
include uv.lock
|
|
5
5
|
include .github/workflows/publish-runtime.yml
|
|
6
6
|
recursive-include docker/runtime *
|
|
7
|
-
recursive-include silicon_cli *.
|
|
7
|
+
recursive-include silicon_cli *.py
|
|
8
8
|
recursive-include tests *.py
|
|
9
9
|
global-exclude *.py[cod]
|
|
10
10
|
global-exclude __pycache__
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: silicon-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.22
|
|
4
4
|
Summary: Silicon CLI — create, run, and manage your silicon instances.
|
|
5
5
|
Author: Saket
|
|
6
6
|
License-Expression: MIT
|
|
@@ -13,7 +13,6 @@ Requires-Python: >=3.9
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: certifi>=2024.2.2
|
|
16
|
-
Requires-Dist: cryptography>=46.0
|
|
17
16
|
Requires-Dist: silicon-extend==0.1.1
|
|
18
17
|
Dynamic: license-file
|
|
19
18
|
|
|
@@ -85,7 +84,8 @@ silicon help Show help
|
|
|
85
84
|
`silicon-browser`, `silicon-extend`, `silicon-interface`, `claude`, or
|
|
86
85
|
`codex`. Shared pip/npm copies update once on the host. Local instance copies
|
|
87
86
|
update only across a safe stop boundary, and Docker copies move through the
|
|
88
|
-
|
|
87
|
+
published, digest-pinned runtime instead of being modified inside a running
|
|
88
|
+
container.
|
|
89
89
|
Glass uses the JSON form of these commands to provide team-scoped inventory
|
|
90
90
|
and durable update jobs.
|
|
91
91
|
|
|
@@ -95,10 +95,7 @@ and durable update jobs.
|
|
|
95
95
|
| --- | --- | --- |
|
|
96
96
|
| `SILICON_HOME` | `~/.silicon` | registry + CLI state |
|
|
97
97
|
| `GLASS_SERVER_URL` | `https://glass.teamofsilicons.com` | Glass sync server (pull/push) |
|
|
98
|
-
| `
|
|
99
|
-
| `SILICON_RELEASE_TRUSTED_KEYS` | packaged production keys | JSON map of key id to base64 raw Ed25519 public key; additive for self-hosting/key rotation |
|
|
100
|
-
| `SILICON_UPDATE_ALLOW_UNSIGNED_GIT` | *(empty)* | explicit compatibility opt-in; select exact-revision Git instead of the signed channel |
|
|
101
|
-
| `SILICON_STEMCELL_REPO` | `teamofsilicons/silicon-stemcell` | base for `new` |
|
|
98
|
+
| `SILICON_STEMCELL_REPO` | `teamofsilicons/silicon-stemcell` | GitHub repository used by `new`, `pull`, and `update` |
|
|
102
99
|
| `SILICON_PYTHON` | `python3` | interpreter used to run a silicon's `main.py` |
|
|
103
100
|
| `SILICON_INTERFACE_CLI_PACKAGE` | `@teamofsilicons/silicon-interface-cli` | npm package used to install the Silicon Interface CLI |
|
|
104
101
|
| `SILICON_INTERFACE_CLI_TARBALL` | versioned npm tarball | fallback package URL if registry metadata is briefly unavailable |
|
|
@@ -106,7 +103,7 @@ and durable update jobs.
|
|
|
106
103
|
| `SILICON_INTERFACE_CLI_SKIP` | *(empty)* | set to `1` to skip interface CLI setup |
|
|
107
104
|
| `SILICON_INTERFACE_DAEMON_SKIP` | *(empty)* | set to `1` to install the CLI without starting its listener daemon |
|
|
108
105
|
| `SILICON_RUNTIME` | *(empty)* | default pull uses Docker; set to `local` to opt out |
|
|
109
|
-
| `SILICON_RUNTIME_IMAGE` |
|
|
106
|
+
| `SILICON_RUNTIME_IMAGE` | published Stemcell tag metadata | bootstrap-only exact digest when no runtime digest is persisted; it cannot retarget an installed release |
|
|
110
107
|
| `SILICON_DOCKER_ROOT` | `~/silicons` | Docker-backed instance root |
|
|
111
108
|
| `SILICON_DOCKER_COMPOSE` | `<root>/compose.yml` | generated Compose file path |
|
|
112
109
|
| `SILICON_DOCKER_SHARED_HOME` | `<root>/.shared-home` | VM-wide Claude/Codex auth home mounted into every container |
|
|
@@ -133,14 +130,15 @@ documented by Docker. The CLI never downloads or executes `get.docker.com` (or
|
|
|
133
130
|
any other mutable installer) as root.
|
|
134
131
|
|
|
135
132
|
`silicon pull` first checks Docker and Compose without selecting an image. It
|
|
136
|
-
then
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
digest
|
|
143
|
-
|
|
133
|
+
then resolves the highest published stable Stemcell Git tag, verifies that
|
|
134
|
+
tag's exact object, commit, version, files, and tree, and reads its exact
|
|
135
|
+
`registry/repository@sha256:<digest>` runtime identity. It pulls the image and
|
|
136
|
+
verifies Docker reports that same repository digest before any instance or
|
|
137
|
+
credential is created. The digest is persisted on each instance and generation;
|
|
138
|
+
updates switch it only at activation, and rollback restores the prior
|
|
139
|
+
generation's digest. A cached image is reusable only when that exact digest
|
|
140
|
+
verifies. Pull completes only after each active Docker Silicon exposes the
|
|
141
|
+
pinned Silicon Extend package and its `silicon-extend` command.
|
|
144
142
|
|
|
145
143
|
```bash
|
|
146
144
|
pip install silicon-cli
|
|
@@ -179,7 +177,7 @@ silicon codex --version
|
|
|
179
177
|
The runtime image contains the complete Silicon runtime contract: Silicon CLI,
|
|
180
178
|
Silicon Browser, Silicon Extend, Silicon Interface CLI, Claude Code, Codex,
|
|
181
179
|
Node 22+, Python tooling, and Git. `silicon pull` verifies every command,
|
|
182
|
-
minimum supported version, and the Python dependency graph in the exact
|
|
180
|
+
minimum supported version, and the Python dependency graph in the exact pinned
|
|
183
181
|
image digest before it asks Glass to claim or register a Silicon. Immutable
|
|
184
182
|
releases use the updater's pre-staged, hash-locked dependency environment. Only
|
|
185
183
|
a legacy flat installation falls back to creating `/silicon/.venv` from its
|
|
@@ -217,8 +215,8 @@ silicon docker compose
|
|
|
217
215
|
```
|
|
218
216
|
|
|
219
217
|
Build and publish the runtime image from this repo, then record the registry's
|
|
220
|
-
immutable digest in
|
|
221
|
-
the matching
|
|
218
|
+
immutable digest as `runtime_image` in the Stemcell's `silicon.info` before
|
|
219
|
+
creating and pushing the matching stable Git tag:
|
|
222
220
|
|
|
223
221
|
```bash
|
|
224
222
|
docker build -f docker/runtime/Dockerfile -t ghcr.io/teamofsilicons/silicon-runtime:<version> .
|
|
@@ -345,7 +343,7 @@ Before stopping anything, the updater verifies a canonical recovery snapshot.
|
|
|
345
343
|
It records the exact services that were running, atomically switches the active
|
|
346
344
|
generation, restores that service state, and requires stable health checks
|
|
347
345
|
before committing. A failure after the stop boundary automatically reactivates
|
|
348
|
-
|
|
346
|
+
a verified reconstruction of the prior generation rather than trusting a
|
|
349
347
|
writable dormant directory. The mutable data root, memory, credentials,
|
|
350
348
|
conversations, and local customization overlay are kept outside immutable
|
|
351
349
|
release generations.
|
|
@@ -367,46 +365,28 @@ Cancellation is accepted only before the service-stop boundary. `resume`
|
|
|
367
365
|
finishes or safely recovers an interrupted update according to its fsynced
|
|
368
366
|
journal; it never guesses from a partially copied directory. Retention keeps
|
|
369
367
|
the active, prior-known-good, and every nonterminal transaction generation even
|
|
370
|
-
when old unreferenced generations are pruned. A durable highest-accepted
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
the
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
1. Generate the Ed25519 keypair offline or in an HSM.
|
|
394
|
-
2. Put only the standard-base64 raw 32-byte public key, under a stable key ID,
|
|
395
|
-
in `silicon_cli/release_trust_anchors.json`.
|
|
396
|
-
3. Run
|
|
397
|
-
`python -m silicon_cli.updater.signatures --check-packaged`; a clean source
|
|
398
|
-
tree intentionally fails this gate until a real production anchor exists.
|
|
399
|
-
4. Build and publish the wheel, then configure Glass's release signer with the
|
|
400
|
-
matching private key from its secret store/HSM.
|
|
401
|
-
5. For rotation, ship and deploy the new public anchor before Glass uses it,
|
|
402
|
-
and retain the previous anchor until every supported CLI has crossed the
|
|
403
|
-
overlap window.
|
|
404
|
-
|
|
405
|
-
For a deliberate compatibility operation,
|
|
406
|
-
`--allow-unsigned-git` (or `SILICON_UPDATE_ALLOW_UNSIGNED_GIT=1`) selects the
|
|
407
|
-
exact-revision Git source. It is never an automatic downgrade. That mode pins a
|
|
408
|
-
single commit and verifies the downloaded tree and archive by SHA-256, but it
|
|
409
|
-
cannot authenticate the publisher and always emits a warning.
|
|
368
|
+
when old unreferenced generations are pruned. A durable highest-accepted
|
|
369
|
+
published-version floor remains in force even after an explicit runtime
|
|
370
|
+
rollback.
|
|
371
|
+
|
|
372
|
+
Git is the sole source of truth for normal Stemcell releases. The updater lists
|
|
373
|
+
the canonical repository's tags, considers only exact stable
|
|
374
|
+
`vMAJOR.MINOR.PATCH` tags, and numerically selects the highest version. It never
|
|
375
|
+
uses `main`, timestamps, prereleases, GitHub's separate “latest release” object,
|
|
376
|
+
or an older fallback tag.
|
|
377
|
+
|
|
378
|
+
The selected tag is pinned to both its advertised tag object and peeled commit.
|
|
379
|
+
The updater fetches that exact ref, detects a tag that moves during the fetch,
|
|
380
|
+
requires `silicon.info.version` to match the tag, requires an immutable runtime
|
|
381
|
+
digest, and seals the downloaded tree into a deterministic SHA-256-verified
|
|
382
|
+
artifact. Candidate Stemcell code is never executed by the updater. A durable
|
|
383
|
+
SemVer floor rejects downgrades and detects a rewritten tag that reuses an
|
|
384
|
+
accepted version with different content.
|
|
385
|
+
|
|
386
|
+
Published stable tags are immutable release records: protect the `v*` tag
|
|
387
|
+
namespace in GitHub and never force-push, move, or reuse one. Build the final
|
|
388
|
+
runtime first, commit its digest and matching version in `silicon.info`, and
|
|
389
|
+
only then create and push the tag.
|
|
410
390
|
|
|
411
391
|
## Implementation notes
|
|
412
392
|
|
|
@@ -66,7 +66,8 @@ silicon help Show help
|
|
|
66
66
|
`silicon-browser`, `silicon-extend`, `silicon-interface`, `claude`, or
|
|
67
67
|
`codex`. Shared pip/npm copies update once on the host. Local instance copies
|
|
68
68
|
update only across a safe stop boundary, and Docker copies move through the
|
|
69
|
-
|
|
69
|
+
published, digest-pinned runtime instead of being modified inside a running
|
|
70
|
+
container.
|
|
70
71
|
Glass uses the JSON form of these commands to provide team-scoped inventory
|
|
71
72
|
and durable update jobs.
|
|
72
73
|
|
|
@@ -76,10 +77,7 @@ and durable update jobs.
|
|
|
76
77
|
| --- | --- | --- |
|
|
77
78
|
| `SILICON_HOME` | `~/.silicon` | registry + CLI state |
|
|
78
79
|
| `GLASS_SERVER_URL` | `https://glass.teamofsilicons.com` | Glass sync server (pull/push) |
|
|
79
|
-
| `
|
|
80
|
-
| `SILICON_RELEASE_TRUSTED_KEYS` | packaged production keys | JSON map of key id to base64 raw Ed25519 public key; additive for self-hosting/key rotation |
|
|
81
|
-
| `SILICON_UPDATE_ALLOW_UNSIGNED_GIT` | *(empty)* | explicit compatibility opt-in; select exact-revision Git instead of the signed channel |
|
|
82
|
-
| `SILICON_STEMCELL_REPO` | `teamofsilicons/silicon-stemcell` | base for `new` |
|
|
80
|
+
| `SILICON_STEMCELL_REPO` | `teamofsilicons/silicon-stemcell` | GitHub repository used by `new`, `pull`, and `update` |
|
|
83
81
|
| `SILICON_PYTHON` | `python3` | interpreter used to run a silicon's `main.py` |
|
|
84
82
|
| `SILICON_INTERFACE_CLI_PACKAGE` | `@teamofsilicons/silicon-interface-cli` | npm package used to install the Silicon Interface CLI |
|
|
85
83
|
| `SILICON_INTERFACE_CLI_TARBALL` | versioned npm tarball | fallback package URL if registry metadata is briefly unavailable |
|
|
@@ -87,7 +85,7 @@ and durable update jobs.
|
|
|
87
85
|
| `SILICON_INTERFACE_CLI_SKIP` | *(empty)* | set to `1` to skip interface CLI setup |
|
|
88
86
|
| `SILICON_INTERFACE_DAEMON_SKIP` | *(empty)* | set to `1` to install the CLI without starting its listener daemon |
|
|
89
87
|
| `SILICON_RUNTIME` | *(empty)* | default pull uses Docker; set to `local` to opt out |
|
|
90
|
-
| `SILICON_RUNTIME_IMAGE` |
|
|
88
|
+
| `SILICON_RUNTIME_IMAGE` | published Stemcell tag metadata | bootstrap-only exact digest when no runtime digest is persisted; it cannot retarget an installed release |
|
|
91
89
|
| `SILICON_DOCKER_ROOT` | `~/silicons` | Docker-backed instance root |
|
|
92
90
|
| `SILICON_DOCKER_COMPOSE` | `<root>/compose.yml` | generated Compose file path |
|
|
93
91
|
| `SILICON_DOCKER_SHARED_HOME` | `<root>/.shared-home` | VM-wide Claude/Codex auth home mounted into every container |
|
|
@@ -114,14 +112,15 @@ documented by Docker. The CLI never downloads or executes `get.docker.com` (or
|
|
|
114
112
|
any other mutable installer) as root.
|
|
115
113
|
|
|
116
114
|
`silicon pull` first checks Docker and Compose without selecting an image. It
|
|
117
|
-
then
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
digest
|
|
124
|
-
|
|
115
|
+
then resolves the highest published stable Stemcell Git tag, verifies that
|
|
116
|
+
tag's exact object, commit, version, files, and tree, and reads its exact
|
|
117
|
+
`registry/repository@sha256:<digest>` runtime identity. It pulls the image and
|
|
118
|
+
verifies Docker reports that same repository digest before any instance or
|
|
119
|
+
credential is created. The digest is persisted on each instance and generation;
|
|
120
|
+
updates switch it only at activation, and rollback restores the prior
|
|
121
|
+
generation's digest. A cached image is reusable only when that exact digest
|
|
122
|
+
verifies. Pull completes only after each active Docker Silicon exposes the
|
|
123
|
+
pinned Silicon Extend package and its `silicon-extend` command.
|
|
125
124
|
|
|
126
125
|
```bash
|
|
127
126
|
pip install silicon-cli
|
|
@@ -160,7 +159,7 @@ silicon codex --version
|
|
|
160
159
|
The runtime image contains the complete Silicon runtime contract: Silicon CLI,
|
|
161
160
|
Silicon Browser, Silicon Extend, Silicon Interface CLI, Claude Code, Codex,
|
|
162
161
|
Node 22+, Python tooling, and Git. `silicon pull` verifies every command,
|
|
163
|
-
minimum supported version, and the Python dependency graph in the exact
|
|
162
|
+
minimum supported version, and the Python dependency graph in the exact pinned
|
|
164
163
|
image digest before it asks Glass to claim or register a Silicon. Immutable
|
|
165
164
|
releases use the updater's pre-staged, hash-locked dependency environment. Only
|
|
166
165
|
a legacy flat installation falls back to creating `/silicon/.venv` from its
|
|
@@ -198,8 +197,8 @@ silicon docker compose
|
|
|
198
197
|
```
|
|
199
198
|
|
|
200
199
|
Build and publish the runtime image from this repo, then record the registry's
|
|
201
|
-
immutable digest in
|
|
202
|
-
the matching
|
|
200
|
+
immutable digest as `runtime_image` in the Stemcell's `silicon.info` before
|
|
201
|
+
creating and pushing the matching stable Git tag:
|
|
203
202
|
|
|
204
203
|
```bash
|
|
205
204
|
docker build -f docker/runtime/Dockerfile -t ghcr.io/teamofsilicons/silicon-runtime:<version> .
|
|
@@ -326,7 +325,7 @@ Before stopping anything, the updater verifies a canonical recovery snapshot.
|
|
|
326
325
|
It records the exact services that were running, atomically switches the active
|
|
327
326
|
generation, restores that service state, and requires stable health checks
|
|
328
327
|
before committing. A failure after the stop boundary automatically reactivates
|
|
329
|
-
|
|
328
|
+
a verified reconstruction of the prior generation rather than trusting a
|
|
330
329
|
writable dormant directory. The mutable data root, memory, credentials,
|
|
331
330
|
conversations, and local customization overlay are kept outside immutable
|
|
332
331
|
release generations.
|
|
@@ -348,46 +347,28 @@ Cancellation is accepted only before the service-stop boundary. `resume`
|
|
|
348
347
|
finishes or safely recovers an interrupted update according to its fsynced
|
|
349
348
|
journal; it never guesses from a partially copied directory. Retention keeps
|
|
350
349
|
the active, prior-known-good, and every nonterminal transaction generation even
|
|
351
|
-
when old unreferenced generations are pruned. A durable highest-accepted
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
the
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
1. Generate the Ed25519 keypair offline or in an HSM.
|
|
375
|
-
2. Put only the standard-base64 raw 32-byte public key, under a stable key ID,
|
|
376
|
-
in `silicon_cli/release_trust_anchors.json`.
|
|
377
|
-
3. Run
|
|
378
|
-
`python -m silicon_cli.updater.signatures --check-packaged`; a clean source
|
|
379
|
-
tree intentionally fails this gate until a real production anchor exists.
|
|
380
|
-
4. Build and publish the wheel, then configure Glass's release signer with the
|
|
381
|
-
matching private key from its secret store/HSM.
|
|
382
|
-
5. For rotation, ship and deploy the new public anchor before Glass uses it,
|
|
383
|
-
and retain the previous anchor until every supported CLI has crossed the
|
|
384
|
-
overlap window.
|
|
385
|
-
|
|
386
|
-
For a deliberate compatibility operation,
|
|
387
|
-
`--allow-unsigned-git` (or `SILICON_UPDATE_ALLOW_UNSIGNED_GIT=1`) selects the
|
|
388
|
-
exact-revision Git source. It is never an automatic downgrade. That mode pins a
|
|
389
|
-
single commit and verifies the downloaded tree and archive by SHA-256, but it
|
|
390
|
-
cannot authenticate the publisher and always emits a warning.
|
|
350
|
+
when old unreferenced generations are pruned. A durable highest-accepted
|
|
351
|
+
published-version floor remains in force even after an explicit runtime
|
|
352
|
+
rollback.
|
|
353
|
+
|
|
354
|
+
Git is the sole source of truth for normal Stemcell releases. The updater lists
|
|
355
|
+
the canonical repository's tags, considers only exact stable
|
|
356
|
+
`vMAJOR.MINOR.PATCH` tags, and numerically selects the highest version. It never
|
|
357
|
+
uses `main`, timestamps, prereleases, GitHub's separate “latest release” object,
|
|
358
|
+
or an older fallback tag.
|
|
359
|
+
|
|
360
|
+
The selected tag is pinned to both its advertised tag object and peeled commit.
|
|
361
|
+
The updater fetches that exact ref, detects a tag that moves during the fetch,
|
|
362
|
+
requires `silicon.info.version` to match the tag, requires an immutable runtime
|
|
363
|
+
digest, and seals the downloaded tree into a deterministic SHA-256-verified
|
|
364
|
+
artifact. Candidate Stemcell code is never executed by the updater. A durable
|
|
365
|
+
SemVer floor rejects downgrades and detects a rewritten tag that reuses an
|
|
366
|
+
accepted version with different content.
|
|
367
|
+
|
|
368
|
+
Published stable tags are immutable release records: protect the `v*` tag
|
|
369
|
+
namespace in GitHub and never force-push, move, or reuse one. Build the final
|
|
370
|
+
runtime first, commit its digest and matching version in `silicon.info`, and
|
|
371
|
+
only then create and push the tag.
|
|
391
372
|
|
|
392
373
|
## Implementation notes
|
|
393
374
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# syntax=docker/dockerfile:1.24.0@sha256:87999aa3d42bdc6bea60565083ee17e86d1f3339802f543c0d03998580f9cb89
|
|
2
2
|
FROM node:22-bookworm-slim@sha256:6c74791e557ce11fc957704f6d4fe134a7bc8d6f5ca4403205b2966bd488f6b3
|
|
3
3
|
|
|
4
|
-
ARG SILICON_CLI_SPEC=silicon-cli==1.0.
|
|
5
|
-
ARG SILICON_BROWSER_SPEC=silicon-browser==1.0.
|
|
4
|
+
ARG SILICON_CLI_SPEC=silicon-cli==1.0.22
|
|
5
|
+
ARG SILICON_BROWSER_SPEC=silicon-browser==1.0.8
|
|
6
6
|
ARG SILICON_EXTEND_SPEC=silicon-extend==0.1.1
|
|
7
|
-
ARG SILICON_INTERFACE_CLI_SPEC=@teamofsilicons/silicon-interface-cli@2.0.
|
|
7
|
+
ARG SILICON_INTERFACE_CLI_SPEC=@teamofsilicons/silicon-interface-cli@2.0.1
|
|
8
8
|
ARG CLAUDE_CODE_SPEC=@anthropic-ai/claude-code@2.1.220
|
|
9
9
|
ARG CODEX_SPEC=@openai/codex@0.145.0
|
|
10
10
|
ARG PIP_SPEC=pip==26.1.2
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "silicon-cli"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.22"
|
|
8
8
|
description = "Silicon CLI — create, run, and manage your silicon instances."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -18,7 +18,6 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
dependencies = [
|
|
20
20
|
"certifi>=2024.2.2",
|
|
21
|
-
"cryptography>=46.0",
|
|
22
21
|
"silicon-extend==0.1.1",
|
|
23
22
|
]
|
|
24
23
|
|
|
@@ -31,6 +30,3 @@ silicon = "silicon_cli.cli:main"
|
|
|
31
30
|
|
|
32
31
|
[tool.setuptools.packages.find]
|
|
33
32
|
include = ["silicon_cli*"]
|
|
34
|
-
|
|
35
|
-
[tool.setuptools.package-data]
|
|
36
|
-
silicon_cli = ["release_trust_anchors.json"]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Silicon CLI — manage silicon instances. Python port of the original bash manager."""
|
|
2
|
-
__version__ = "1.0.
|
|
2
|
+
__version__ = "1.0.22"
|
|
@@ -293,7 +293,6 @@ def cmd_help() -> None:
|
|
|
293
293
|
silicon backup [name] [now|stop|status] Alias for silicon push
|
|
294
294
|
silicon update <target> Stage, drain, checkpoint, update, validate, and restart
|
|
295
295
|
silicon update --dry-run <target> Show the exact merge/update plan
|
|
296
|
-
silicon update --allow-unsigned-git <target> Explicit compatibility source
|
|
297
296
|
silicon update status [name] Show generation and transaction state
|
|
298
297
|
silicon update cancel [name] Cancel an update before its stop boundary
|
|
299
298
|
silicon update resume [name] Resume an interrupted transaction
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import re
|
|
7
8
|
import shutil
|
|
8
9
|
import stat
|
|
9
10
|
from pathlib import Path
|
|
@@ -14,18 +15,23 @@ REGISTRY_FILE = REGISTRY_DIR / "registry.json"
|
|
|
14
15
|
|
|
15
16
|
# Glass sync server (pull/push). Override with GLASS_SERVER_URL to point elsewhere.
|
|
16
17
|
GLASS_SERVER_URL = os.environ.get("GLASS_SERVER_URL", "https://glass.teamofsilicons.com").rstrip("/")
|
|
17
|
-
SILICON_RELEASE_MANIFEST_URL = os.environ.get(
|
|
18
|
-
"SILICON_RELEASE_MANIFEST_URL",
|
|
19
|
-
f"{GLASS_SERVER_URL}/api/v1/silicon-release/latest.json",
|
|
20
|
-
)
|
|
21
|
-
SILICON_UPDATE_ALLOW_UNSIGNED_GIT = os.environ.get(
|
|
22
|
-
"SILICON_UPDATE_ALLOW_UNSIGNED_GIT", ""
|
|
23
|
-
).lower() in {"1", "true", "yes", "on"}
|
|
24
18
|
|
|
25
|
-
# Stemcell — the
|
|
19
|
+
# Stemcell — stable SemVer tags are the source for new and updated Silicons.
|
|
26
20
|
STEMCELL_REPO = os.environ.get("SILICON_STEMCELL_REPO", "teamofsilicons/silicon-stemcell")
|
|
21
|
+
_stemcell_repository_parts = STEMCELL_REPO.split("/")
|
|
22
|
+
if (
|
|
23
|
+
re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", STEMCELL_REPO)
|
|
24
|
+
is None
|
|
25
|
+
or len(_stemcell_repository_parts) != 2
|
|
26
|
+
or any(
|
|
27
|
+
part in {".", ".."} or len(part) > 100
|
|
28
|
+
for part in _stemcell_repository_parts
|
|
29
|
+
)
|
|
30
|
+
):
|
|
31
|
+
raise RuntimeError(
|
|
32
|
+
"SILICON_STEMCELL_REPO must be one GitHub owner/repository pair"
|
|
33
|
+
)
|
|
27
34
|
STEMCELL_GIT_URL = f"https://github.com/{STEMCELL_REPO}.git"
|
|
28
|
-
STEMCELL_ZIP_URL = f"https://github.com/{STEMCELL_REPO}/archive/refs/heads/main.zip"
|
|
29
35
|
|
|
30
36
|
# Silicon Interface CLI. During local development, silicon-cli will auto-detect
|
|
31
37
|
# a sibling silicon-interface checkout; in production this package spec is used.
|
|
@@ -35,7 +41,7 @@ SILICON_INTERFACE_CLI_PACKAGE = os.environ.get(
|
|
|
35
41
|
)
|
|
36
42
|
SILICON_INTERFACE_CLI_TARBALL = os.environ.get(
|
|
37
43
|
"SILICON_INTERFACE_CLI_TARBALL",
|
|
38
|
-
"https://registry.npmjs.org/@teamofsilicons/silicon-interface-cli/-/silicon-interface-cli-2.0.
|
|
44
|
+
"https://registry.npmjs.org/@teamofsilicons/silicon-interface-cli/-/silicon-interface-cli-2.0.1.tgz",
|
|
39
45
|
)
|
|
40
46
|
SILICON_INTERFACE_CLI_SOURCE = os.environ.get("SILICON_INTERFACE_CLI_SOURCE", "")
|
|
41
47
|
SILICON_INTERFACE_CLI_SKIP = os.environ.get("SILICON_INTERFACE_CLI_SKIP", "").lower() in {
|
|
@@ -128,8 +128,8 @@ def _require_runtime_image(image: object, *, context: str) -> str:
|
|
|
128
128
|
)
|
|
129
129
|
return value
|
|
130
130
|
raise RuntimeError(
|
|
131
|
-
f"{context} has no
|
|
132
|
-
"Silicon release first; the image must be "
|
|
131
|
+
f"{context} has no published, immutable runtime image. Fetch a "
|
|
132
|
+
"published Silicon release first; the image must be "
|
|
133
133
|
"registry/repository@sha256:<digest>. Mutable tags such as :latest "
|
|
134
134
|
f"are refused. Set {UNPINNED_IMAGE_OPT_IN}=1 only for isolated local "
|
|
135
135
|
"development."
|
|
@@ -240,8 +240,8 @@ def init(
|
|
|
240
240
|
ui.info(f"Runtime image: {chosen_image}")
|
|
241
241
|
else:
|
|
242
242
|
ui.info(
|
|
243
|
-
"Runtime image: awaiting the digest
|
|
244
|
-
"
|
|
243
|
+
"Runtime image: awaiting the digest committed to the "
|
|
244
|
+
"published Stemcell tag"
|
|
245
245
|
)
|
|
246
246
|
if docker_sudo:
|
|
247
247
|
ui.info("Docker commands will run through sudo for this runtime.")
|
|
@@ -508,7 +508,7 @@ def _ensure_image(config: dict, *, refresh: bool = False) -> None:
|
|
|
508
508
|
ui.info("Using the existing local-development image.")
|
|
509
509
|
return
|
|
510
510
|
raise RuntimeError(
|
|
511
|
-
f"Could not pull and verify the
|
|
511
|
+
f"Could not pull and verify the pinned Docker image: {image}. "
|
|
512
512
|
"Authenticate to its registry if it is private, then retry."
|
|
513
513
|
)
|
|
514
514
|
|
|
@@ -566,13 +566,13 @@ def ensure_ready(
|
|
|
566
566
|
|
|
567
567
|
|
|
568
568
|
def prepare_release_image(image: str) -> dict:
|
|
569
|
-
"""Pull and verify a
|
|
569
|
+
"""Pull and verify a published image without changing active runtime state."""
|
|
570
570
|
|
|
571
571
|
pinned = str(image or "").strip()
|
|
572
572
|
if not runtime_image_is_pinned(pinned):
|
|
573
573
|
raise RuntimeError(
|
|
574
|
-
"Docker releases require
|
|
575
|
-
"
|
|
574
|
+
"Docker releases require the immutable runtime image digest "
|
|
575
|
+
"committed in the published Stemcell Git tag"
|
|
576
576
|
)
|
|
577
577
|
cfg = {**load_config(required=True), "image": pinned}
|
|
578
578
|
_ensure_image(cfg)
|
|
@@ -592,6 +592,8 @@ def inspect_runtime_contract(config: dict, image: str) -> dict:
|
|
|
592
592
|
*_docker_cmd(config),
|
|
593
593
|
"run",
|
|
594
594
|
"--rm",
|
|
595
|
+
"--pull",
|
|
596
|
+
"never",
|
|
595
597
|
"--entrypoint",
|
|
596
598
|
"/opt/silicon-runtime/bin/python",
|
|
597
599
|
selected,
|
|
@@ -627,7 +629,7 @@ def inspect_runtime_contract(config: dict, image: str) -> dict:
|
|
|
627
629
|
|
|
628
630
|
|
|
629
631
|
def verify_runtime_contract(config: dict, image: str) -> dict[str, str]:
|
|
630
|
-
"""Prove the
|
|
632
|
+
"""Prove the pinned image contains the complete supported toolchain."""
|
|
631
633
|
|
|
632
634
|
selected = _require_runtime_image(
|
|
633
635
|
image,
|
|
@@ -641,19 +643,19 @@ def verify_runtime_contract(config: dict, image: str) -> dict[str, str]:
|
|
|
641
643
|
else:
|
|
642
644
|
detail = "the runtime probe failed without structured diagnostics"
|
|
643
645
|
raise RuntimeError(
|
|
644
|
-
"the
|
|
646
|
+
"the published Silicon runtime image is missing or has outdated "
|
|
645
647
|
f"required dependencies: {detail}. Publish a fresh runtime image "
|
|
646
648
|
"containing Silicon CLI, Silicon Browser, Silicon Extend, Silicon "
|
|
647
649
|
"Interface CLI, Claude Code, Codex, Node 22+, Python, and Git; "
|
|
648
|
-
"then
|
|
650
|
+
"then commit that digest in the published Stemcell tag."
|
|
649
651
|
)
|
|
650
652
|
versions = payload.get("versions")
|
|
651
653
|
if not isinstance(versions, dict):
|
|
652
654
|
raise RuntimeError(
|
|
653
|
-
"the
|
|
655
|
+
"the published Silicon runtime dependency probe returned no version "
|
|
654
656
|
"inventory"
|
|
655
657
|
)
|
|
656
|
-
ui.success("Verified the complete
|
|
658
|
+
ui.success("Verified the complete published Silicon runtime toolchain.")
|
|
657
659
|
return {
|
|
658
660
|
str(name): str(version)
|
|
659
661
|
for name, version in versions.items()
|
|
@@ -666,7 +668,7 @@ def bind_release_runtime(
|
|
|
666
668
|
installs: Iterable[registry.Install] = (),
|
|
667
669
|
pull: bool = True,
|
|
668
670
|
) -> dict:
|
|
669
|
-
"""Verify and persist the exact image
|
|
671
|
+
"""Verify and persist the exact image committed by a release.
|
|
670
672
|
|
|
671
673
|
The image is pulled and content-address verified before either config or
|
|
672
674
|
registry metadata changes. With no selected install this establishes the
|
|
@@ -684,8 +686,8 @@ def bind_release_runtime(
|
|
|
684
686
|
else:
|
|
685
687
|
if not runtime_image_is_pinned(pinned):
|
|
686
688
|
raise RuntimeError(
|
|
687
|
-
"Docker releases require
|
|
688
|
-
"
|
|
689
|
+
"Docker releases require the immutable runtime image digest "
|
|
690
|
+
"committed in the published Stemcell Git tag"
|
|
689
691
|
)
|
|
690
692
|
candidate = {
|
|
691
693
|
**load_config(required=True),
|
|
@@ -725,7 +727,7 @@ def bind_release_runtime(
|
|
|
725
727
|
|
|
726
728
|
|
|
727
729
|
def active_generation_runtime_image(inst: registry.Install) -> str:
|
|
728
|
-
"""Return the
|
|
730
|
+
"""Return the pinned image bound to the selected code generation."""
|
|
729
731
|
|
|
730
732
|
from .updater.generation import GenerationError, GenerationStore
|
|
731
733
|
|
|
@@ -945,10 +947,9 @@ def ensure_pull_runtime() -> bool:
|
|
|
945
947
|
if runtime_opted_out():
|
|
946
948
|
ui.info("SILICON_RUNTIME is set to local/host; pulling without Docker runtime.")
|
|
947
949
|
return False
|
|
948
|
-
# At this point no
|
|
950
|
+
# At this point no published Stemcell tag has been fetched yet, so there is
|
|
949
951
|
# deliberately no image to pull. ``stemcell.prepare_hydration`` binds the
|
|
950
|
-
# digest
|
|
951
|
-
# written.
|
|
952
|
+
# digest committed to that exact tag before any target or secret is written.
|
|
952
953
|
ensure_ready(
|
|
953
954
|
auto_init=True,
|
|
954
955
|
install=True,
|
|
@@ -1467,7 +1468,7 @@ if (
|
|
|
1467
1468
|
raise RuntimeError(
|
|
1468
1469
|
f"Docker Silicon '{inst.name}' does not have the required "
|
|
1469
1470
|
f"Silicon Extend {SILICON_EXTEND_VERSION} runtime. Ensure the "
|
|
1470
|
-
"
|
|
1471
|
+
"published runtime image and Stemcell requirements lock include it, "
|
|
1471
1472
|
"then rerun the same pull."
|
|
1472
1473
|
)
|
|
1473
1474
|
|
|
@@ -1486,7 +1487,7 @@ def prepare_environment(
|
|
|
1486
1487
|
if not lockfile.is_file():
|
|
1487
1488
|
if requirements.is_file():
|
|
1488
1489
|
raise RuntimeError(
|
|
1489
|
-
"
|
|
1490
|
+
"published Docker release has requirements.txt but no "
|
|
1490
1491
|
"hash-pinned requirements.lock"
|
|
1491
1492
|
)
|
|
1492
1493
|
return None
|
|
@@ -1952,7 +1953,7 @@ def cmd_docker(args: list[str]) -> None:
|
|
|
1952
1953
|
render_compose(cfg)
|
|
1953
1954
|
else:
|
|
1954
1955
|
ui.info(
|
|
1955
|
-
"Docker prerequisites are ready. The next
|
|
1956
|
+
"Docker prerequisites are ready. The next published `silicon "
|
|
1956
1957
|
"pull` binds and pulls its immutable runtime image digest."
|
|
1957
1958
|
)
|
|
1958
1959
|
return
|