Put with Files: choose kits, as Manage Kits does, and rebuild kits.scode

A kits step after the zip is chosen, laid out as the Manage Kits panel:
installed kits (what the app needs, greyed), installed but not used, kits
available on the computer, not supported; the Action cell offers Keep at /
Upgrade to / Downgrade to / Add at / REMOVE; Upgrade all kits to latest
version. Kits not in the backup can be added, with their dependencies.

The ticked kits make app.sab's schema (encode_sax takes the kit list, as
Ontrol's PutApp rewrites it). When they are not the device's kits, or
Rebuild kits.scode is ticked, kits.scode is built for them with Ontrol's
sedonac from the Niagara install, after checking the natives the kits call
against the device's platform manifest (PutApp.checkNativeCompatibility).

- kit_build.py: platform manifest lookup, native check, sedonac run
- palette.kit_versions: a checksum released more than once stands for its
  highest version (ontrolPlatform 1.0.1, not 1.0.0)
- palette.unsupported_kits takes the kit list to judge against
- sab_app: a type from a kit the app does not list is a clear error

Tested on a P-ION: a rebuild for its own kits is byte-identical to the one
it runs; adding and then removing the logic kit both come back running.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 10:37:08 +03:00
co-authored by Claude Opus 5.5
parent 08b4dec532
commit e070bd0f1e
7 changed files with 776 additions and 119 deletions
+12 -6
View File
@@ -80,6 +80,9 @@ def _resolve_slots(qname, types_by_q, cache):
"""The type's slots, base chain first, indexed by slot id."""
if qname in cache:
return cache[qname]
if qname not in types_by_q:
raise SabError(f"type {qname} is not in the app's kits -- a kit build that "
f"needs kit '{qname.split('::')[0]}', which the app does not list?")
t = types_by_q[qname]
base = t.get("base")
out = list(_resolve_slots(base, types_by_q, cache)) if base else []
@@ -505,21 +508,24 @@ def _encode_value(out, slot, raw):
raise SabError(f"unhandled slot type {vtype}")
def encode_sax(sax, checksums=None):
def encode_sax(sax, checksums=None, kits=None):
"""app.sax (text or bytes) -> app.sab bytes, as `sedonac app.sax` writes it.
checksums, {kit name: checksum hex}, stands in for the sax's own -- how an app goes
to a device whose kits are other builds than the ones it was saved with. Every
kit's manifest must be in Sedona home under the checksum used.
to a device whose kits are other builds than the ones it was saved with. kits,
[(name, checksum hex)], replaces the schema altogether, as Ontrol's PutApp rewrites
it for the kits chosen to put. Every kit's manifest must be in Sedona home under
the checksum used.
"""
root = ET.fromstring(sax)
schema_elem, app_elem = root.find("schema"), root.find("app")
if schema_elem is None or app_elem is None:
raise SabError("not a Sedona app: no <schema> or <app>")
if kits is None:
kits = [(k.get("name"), (checksums or {}).get(k.get("name")) or k.get("checksum"))
for k in schema_elem.findall("kit")]
parts = []
for k in schema_elem.findall("kit"):
name = k.get("name")
cks = (checksums or {}).get(name) or k.get("checksum")
for name, cks in kits:
if not cks:
raise SabError(f"no checksum for kit '{name}'")
parts.append((name, f"{int(cks, 16):08x}"))