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.
Files changed (78) hide show
  1. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/.github/workflows/publish-runtime.yml +0 -5
  2. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/MANIFEST.in +1 -1
  3. {silicon_cli-1.0.20/silicon_cli.egg-info → silicon_cli-1.0.22}/PKG-INFO +40 -60
  4. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/README.md +39 -58
  5. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/docker/runtime/Dockerfile +3 -3
  6. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/pyproject.toml +1 -5
  7. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/__init__.py +1 -1
  8. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/cli.py +0 -1
  9. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/config.py +16 -10
  10. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/docker_runtime.py +24 -23
  11. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/package_manager.py +36 -25
  12. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/runtime_contract.py +4 -4
  13. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/stemcell.py +9 -16
  14. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/sync.py +1 -1
  15. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/update.py +12 -22
  16. silicon_cli-1.0.22/silicon_cli/updater/channel.py +59 -0
  17. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/engine.py +11 -30
  18. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/generation.py +97 -70
  19. silicon_cli-1.0.22/silicon_cli/updater/release.py +1157 -0
  20. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/retention.py +1 -1
  21. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/snapshot_adapter.py +34 -61
  22. {silicon_cli-1.0.20 → silicon_cli-1.0.22/silicon_cli.egg-info}/PKG-INFO +40 -60
  23. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/SOURCES.txt +1 -3
  24. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/requires.txt +0 -1
  25. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_docker_runtime.py +3 -3
  26. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_docker_update_hooks.py +16 -2
  27. silicon_cli-1.0.22/tests/test_git_release_source.py +440 -0
  28. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_package_manager.py +19 -1
  29. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_runtime_contract.py +2 -2
  30. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_snapshot_adapter.py +1 -1
  31. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_stemcell_hydration.py +5 -3
  32. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_transactional_updater.py +246 -1
  33. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_update.py +63 -9
  34. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/uv.lock +1 -4
  35. silicon_cli-1.0.20/silicon_cli/release_trust_anchors.json +0 -6
  36. silicon_cli-1.0.20/silicon_cli/updater/channel.py +0 -68
  37. silicon_cli-1.0.20/silicon_cli/updater/release.py +0 -772
  38. silicon_cli-1.0.20/silicon_cli/updater/signatures.py +0 -226
  39. silicon_cli-1.0.20/tests/test_signed_release_source.py +0 -373
  40. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/LICENSE +0 -0
  41. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/docker/runtime/Dockerfile.local +0 -0
  42. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/docker/runtime/runtime-entrypoint.sh +0 -0
  43. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/setup.cfg +0 -0
  44. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/backup_runtime.py +0 -0
  45. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/glassagent.py +0 -0
  46. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/host_lock.py +0 -0
  47. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/http_transport.py +0 -0
  48. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/interface_cli.py +0 -0
  49. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/process.py +0 -0
  50. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/pull_transaction.py +0 -0
  51. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/registry.py +0 -0
  52. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/ui.py +0 -0
  53. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/__init__.py +0 -0
  54. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/cache.py +0 -0
  55. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/checkpoint.py +0 -0
  56. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/fleet.py +0 -0
  57. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/io.py +0 -0
  58. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/journal.py +0 -0
  59. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/lock.py +0 -0
  60. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/maintenance.py +0 -0
  61. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/overlay.py +0 -0
  62. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/planner.py +0 -0
  63. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli/updater/policy.py +0 -0
  64. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/dependency_links.txt +0 -0
  65. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/entry_points.txt +0 -0
  66. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/silicon_cli.egg-info/top_level.txt +0 -0
  67. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_backup_runtime.py +0 -0
  68. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_browser_profile.py +0 -0
  69. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_extend_installation.py +0 -0
  70. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_fleet_update_journal.py +0 -0
  71. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_glassagent_process.py +0 -0
  72. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_http_transport.py +0 -0
  73. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_process_health.py +0 -0
  74. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_pull_args.py +0 -0
  75. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_pull_transaction.py +0 -0
  76. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_sync_credentials_backup.py +0 -0
  77. {silicon_cli-1.0.20 → silicon_cli-1.0.22}/tests/test_update_maintenance.py +0 -0
  78. {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 *.json *.py
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.20
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
- signed runtime release instead of being modified inside a running container.
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
- | `SILICON_RELEASE_MANIFEST_URL` | `<GLASS_SERVER_URL>/api/v1/silicon-release/latest.json` | signed schema-1 release manifest |
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` | signed release manifest | bootstrap-only exact digest when no runtime digest is persisted; it cannot retarget an installed release |
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 verifies the signed Silicon release manifest, reads the exact
137
- `registry/repository@sha256:<digest>` runtime identity covered by that signature,
138
- pulls it, and verifies Docker reports that same repository digest before any
139
- instance or credential is created. The digest is persisted on each instance and
140
- generation; updates switch it only at activation, and rollback restores the
141
- prior generation's digest. A cached image is reusable only when that exact
142
- digest verifies. Pull completes only after each active Docker Silicon exposes
143
- the pinned Silicon Extend package and its `silicon-extend` command.
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 signed
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 Glass as `SILICON_RELEASE_RUNTIME_IMAGE` before publishing
221
- the matching Stemcell release:
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
- an authenticated reconstruction of the prior generation rather than trusting a
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 signed
371
- release sequence remains in force even after an explicit runtime rollback.
372
-
373
- The default release channel accepts only an expiry-checked HTTPS manifest with
374
- at least one valid Ed25519 signature from a pinned public key. The manifest pins
375
- the exact content-addressed artifact and every extracted file; candidate code is
376
- never executed to perform or validate the update. Key rotation is additive:
377
- ship a new `key_id` and raw 32-byte public key in the CLI keyring before Glass
378
- begins signing with it, retain the old public key through the supported-client
379
- window, then remove it in a later CLI release. A self-hosted public-only keyring
380
- uses this operator-ready format:
381
-
382
- ```bash
383
- export SILICON_RELEASE_TRUSTED_KEYS='{"release-2026-01":"<base64 of the raw 32-byte Ed25519 public key>"}'
384
- ```
385
-
386
- The matching private key belongs only in Glass's secret store as
387
- `SILICON_RELEASE_SIGNING_PRIVATE_KEY`; it must never be placed in this CLI,
388
- an instance directory, or the JSON above. Until the production key is securely
389
- provisioned and its public half is packaged, signed updates fail closed.
390
-
391
- Production release-key handoff is deliberately explicit:
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
- signed runtime release instead of being modified inside a running container.
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
- | `SILICON_RELEASE_MANIFEST_URL` | `<GLASS_SERVER_URL>/api/v1/silicon-release/latest.json` | signed schema-1 release manifest |
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` | signed release manifest | bootstrap-only exact digest when no runtime digest is persisted; it cannot retarget an installed release |
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 verifies the signed Silicon release manifest, reads the exact
118
- `registry/repository@sha256:<digest>` runtime identity covered by that signature,
119
- pulls it, and verifies Docker reports that same repository digest before any
120
- instance or credential is created. The digest is persisted on each instance and
121
- generation; updates switch it only at activation, and rollback restores the
122
- prior generation's digest. A cached image is reusable only when that exact
123
- digest verifies. Pull completes only after each active Docker Silicon exposes
124
- the pinned Silicon Extend package and its `silicon-extend` command.
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 signed
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 Glass as `SILICON_RELEASE_RUNTIME_IMAGE` before publishing
202
- the matching Stemcell release:
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
- an authenticated reconstruction of the prior generation rather than trusting a
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 signed
352
- release sequence remains in force even after an explicit runtime rollback.
353
-
354
- The default release channel accepts only an expiry-checked HTTPS manifest with
355
- at least one valid Ed25519 signature from a pinned public key. The manifest pins
356
- the exact content-addressed artifact and every extracted file; candidate code is
357
- never executed to perform or validate the update. Key rotation is additive:
358
- ship a new `key_id` and raw 32-byte public key in the CLI keyring before Glass
359
- begins signing with it, retain the old public key through the supported-client
360
- window, then remove it in a later CLI release. A self-hosted public-only keyring
361
- uses this operator-ready format:
362
-
363
- ```bash
364
- export SILICON_RELEASE_TRUSTED_KEYS='{"release-2026-01":"<base64 of the raw 32-byte Ed25519 public key>"}'
365
- ```
366
-
367
- The matching private key belongs only in Glass's secret store as
368
- `SILICON_RELEASE_SIGNING_PRIVATE_KEY`; it must never be placed in this CLI,
369
- an instance directory, or the JSON above. Until the production key is securely
370
- provisioned and its public half is packaged, signed updates fail closed.
371
-
372
- Production release-key handoff is deliberately explicit:
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.20
5
- ARG SILICON_BROWSER_SPEC=silicon-browser==1.0.7
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.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.20"
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.20"
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 base every new silicon is hydrated from.
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.0.tgz",
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 signed, immutable runtime image. Fetch a signed "
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 from the signed Silicon "
244
- "release channel"
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 signed Docker image: {image}. "
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 signed image without changing active runtime state."""
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 a signed immutable runtime image digest; "
575
- "unsigned Git releases cannot select a Docker runtime"
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 signed image contains the complete supported toolchain."""
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 signed Silicon runtime image is missing or has outdated "
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 publish that digest in the signed Silicon release."
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 signed Silicon runtime dependency probe returned no version "
655
+ "the published Silicon runtime dependency probe returned no version "
654
656
  "inventory"
655
657
  )
656
- ui.success("Verified the complete signed Silicon runtime toolchain.")
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 authenticated by a release.
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 a signed immutable runtime image "
688
- "digest"
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 signed image bound to the selected code generation."""
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 authenticated release has been fetched yet, so there is
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 from the verified signed manifest before any target or secret is
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
- "signed runtime image and Stemcell requirements lock include it, "
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
- "signed Docker release has requirements.txt but no "
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 signed `silicon "
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