Skip to content
Merged
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
37 changes: 37 additions & 0 deletions .trinity/seals/t27os_T27osKeys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"built_by": "t27c-bootstrap@0.5.2+42a4860c4",
"closure": [
{
"spec": "specs/t27os/agents.t27",
"spec_hash": "sha256:c4b89c1bd1dcb751fada2ae1f0330e250d17c5085ea73acdc38fac13548277ee"
},
{
"spec": "specs/t27os/session_store.t27",
"spec_hash": "sha256:7d05d7dda14587458a77291c3d587e3f08516d8e678f0a4bd06dcaab7f6d1f2f"
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:4d2ce37475c5fd9cfdbe564d68df7c92327f40be8d60faa04aa8382217dc60c5",
"gen_hash_rust": "sha256:65387d614d4714c7dff0227fb378c33bc7886c695cc42f4ea24458223de573c8",
"gen_hash_verilog": "sha256:9e29c63b98f00cd92e6b0466d356a9d3911bf444f26a652d5e2e6e6b576c6ade",
"gen_hash_zig": "sha256:d244fcdfb0f95fcf8a2742598dbc84d7e66c6c3aec8b4bc632483753564f8230",
"module": "T27osKeys",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-20T07:00:49Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:bc12487689ca25d46a0155a6cbb30e36983c4898a7e28486266d084c570f8d43",
"spec_path": "specs/t27os/keys.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 63,
"total": 63,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
53 changes: 53 additions & 0 deletions .trinity/seals/t27os_T27osPtyLogic.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"built_by": "t27c-bootstrap@0.5.2+42a4860c4",
"closure": [
{
"spec": "specs/t27os/agents.t27",
"spec_hash": "sha256:c4b89c1bd1dcb751fada2ae1f0330e250d17c5085ea73acdc38fac13548277ee"
},
{
"spec": "specs/t27os/editors.t27",
"spec_hash": "sha256:9b4e3c083e7aa0d3d3b42ac77f6349820e123d34617abbdfc85358cd22420ede"
},
{
"spec": "specs/t27os/keys.t27",
"spec_hash": "sha256:bc12487689ca25d46a0155a6cbb30e36983c4898a7e28486266d084c570f8d43"
},
{
"spec": "specs/t27os/pane_state.t27",
"spec_hash": "sha256:c7c7ca610b0f69c66dd5518c93f045bfa3f666c1849c12ca2de0c6542d73d1fc"
},
{
"spec": "specs/t27os/pty_proto.t27",
"spec_hash": "sha256:fe9dee96f99eccaa6273124bb9022a66ca916bc9a0f6c3f415c2f4b6ab964ab1"
},
{
"spec": "specs/t27os/session_store.t27",
"spec_hash": "sha256:7d05d7dda14587458a77291c3d587e3f08516d8e678f0a4bd06dcaab7f6d1f2f"
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:a1ba1cc53e826cbfe4123efe840b6b7d8ba3b3b9bcec14bcea82a64e6d77bad7",
"gen_hash_rust": "sha256:b1f9544872abbca303b2b4565a357e4134da53bf232b7efcf2d729d13c3a987a",
"gen_hash_verilog": "sha256:9c28b828e7de42515be178c72ec47fea0b3a20cb6840e9c1a4fbf8085862e08d",
"gen_hash_zig": "sha256:39ac7d141b3d9832cc4ec5e92e9f73e2cf030c4379c4f3253696d46f159efb62",
"module": "T27osPtyLogic",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-20T07:00:53Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:401e66c84055044c98546c093cbdb677ae5cb5203d524b01e172b37a7afc1e33",
"spec_path": "specs/t27os/pty_logic.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 34,
"total": 34,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
78 changes: 58 additions & 20 deletions specs/t27os/glass_window.t27
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ use t27os::layout::cap_mid;
use t27os::layout::same_line;
use t27os::agents::AGENT_NAMES;
use t27os::keys::PROVIDER_COUNT;
use t27os::keys::PROFILE_COUNT;
use t27os::keys::MAX_KEYS;
use t27os::keys::setkey_byte;
use t27os::keys::key_count_text;
use t27os::keys::MAX_KEY_CHARS;
use t27os::keys::KEYS_WORDS;
use t27os::keys::KY_NONE;
Expand Down Expand Up @@ -112,7 +116,8 @@ use t27os::keys::circuit_fails;
use t27os::keys::status_shown;
use t27os::keys_host::kc_put;
use t27os::keys_host::kc_get;
use t27os::keys_host::kc_del;
use t27os::keys_host::kc_get_n;
use t27os::keys_host::kc_del_all;
use t27os::keys_host::kc_has;
use t27os::keys_host::probe;
use t27os::keys_host::push_keys;
Expand Down Expand Up @@ -2328,29 +2333,46 @@ fn bytes_w(kit: [48]u64, s: []const u8, n: usize, size: f64, weight: f64, mono:
return w;
}

/// The Keychain is read here and nowhere else in the window: which providers hold a key, and the masked
/// text of each (16 bytes a provider). The full key is wiped from the buffer before the next one is read.
/// The Keychain is read here and nowhere else in the window: how many keys each provider holds (stored[p],
/// 0 to MAX_KEYS, every numbered account is read) and the masked text of its first key (16 bytes a
/// provider). The full key is wiped from the buffer before the next one is read.
fn keys_refresh(stored: []u32, mask: []u8, mlen: []u32) -> void {
var key : [256]u8 = undefined;
var p : u32 = 0;
while (p < PROVIDER_COUNT) {
const n : usize = kc_get(p, key[0..256]);
const at : usize = @as(usize, p) * 16;
mlen[p] = 0;
stored[p] = 0;
if (n > 0) {
stored[p] = 1;
mlen[p] = @as(u32, @intCast(mask_key(key[0..n], mask[at .. at + 16])));
}
var i : usize = 0;
while (i < 256) {
key[i] = 0;
i = i + 1;
var k : u32 = 0;
while (k < MAX_KEYS) {
const n : usize = kc_get_n(p, k, key[0..256]);
if (n > 0) {
if (stored[p] == 0) { mlen[p] = @as(u32, @intCast(mask_key(key[0..n], mask[at .. at + 16]))); }
stored[p] = stored[p] + 1;
}
var i : usize = 0;
while (i < 256) {
key[i] = 0;
i = i + 1;
}
k = k + 1;
}
p = p + 1;
}
}

/// Hand the server every stored key with its number (keys_host push_keys), then take back the first key of each
/// provider whose last check failed, as the check button does: a refresh must not undo that.
fn keys_sync(fd: i32, kst: []const u32, out: []u8) -> void {
push_keys(fd);
const bad : u32 = bad_mask(kst);
var p : u32 = 0;
while (p < PROVIDER_COUNT) {
if ((bad >> p) % 2 == 1) { client_set_key(fd, setkey_byte(p, 0), "", out); }
p = p + 1;
}
}

fn keys_wipe(buf: []u8) -> void {
var i : usize = 0;
while (i < buf.len) {
Expand Down Expand Up @@ -2412,11 +2434,18 @@ fn draw_keys_panel(c: u64, kit: [48]u64, sw: f64, sh: f64, mx: f64, my: f64, sto
var p : u32 = 0;
while (p < PROVIDER_COUNT) {
const y : f64 = ky_row_top(t, p);
const has : bool = stored[p] == 1;
const has : bool = stored[p] > 0;
const st : u32 = status_shown(has, kst[p], kfail[p], kopen[p], now);
cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE);
n = provider_name(p, w[0..48]);
draw_bytes(c, kit, w[0..48], n, x, y + 14.0, 11.0, WEIGHT_SEMIBOLD, false);
const nw : f64 = draw_bytes(c, kit, w[0..48], n, x, y + 14.0, 11.0, WEIGHT_SEMIBOLD, false);
// how many keys the provider holds ("zai 7 keys"), right after its name
n = key_count_text(stored[p], w[0..48]);
if (n > 0) {
cg_fill_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE);
draw_bytes(c, kit, w[0..48], n, x + nw + 8.0, y + 14.0, 9.5, 0.0, false);
}
cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE);
draw_dot(c, status_dot(st), x + 112.0, y + 10.0, 1.0);
n = csv_field(STATUS_NAMES, st, w[0..48]);
cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE);
Expand Down Expand Up @@ -2454,7 +2483,7 @@ fn draw_keys_panel(c: u64, kit: [48]u64, sw: f64, sh: f64, mx: f64, my: f64, sto
var sm : u32 = 0;
var pi : u32 = 0;
while (pi < PROVIDER_COUNT) {
if (stored[pi] == 1) { sm = sm + (1 << pi); }
if (stored[pi] > 0) { sm = sm + (1 << pi); }
pi = pi + 1;
}
var wy : [96]u8 = undefined;
Expand Down Expand Up @@ -3761,7 +3790,7 @@ pub fn main() -> void {
const kh : u32 = ky_hit(mx, my, kl, QP_W, QP_PAD, WS_TOP);
const kq : u32 = kh / 4;
if (kh != KY_NONE) {
const kact : u32 = ky_click(kh % 4, kstored[kq] == 1, probe_has(kq), karm, kq);
const kact : u32 = ky_click(kh % 4, kstored[kq] > 0, probe_has(kq), karm, kq);
if (kact == KA_EDIT) {
kedit = kq;
klen = 0;
Expand All @@ -3771,12 +3800,19 @@ pub fn main() -> void {
kedit = KY_NONE;
klen = 0;
} else if (kact == KA_DELETE) {
kc_del(kq);
// the row's DELETE removes every key of the provider, from the Keychain and the server
kc_del_all(kq);
karm = KY_NONE;
kst[kq] = ST_STORED;
kfail[kq] = 0;
keys_refresh(kstored[0..9], kmask[0..144], kmlen[0..9]);
if (tfd >= 0) { client_set_key(tfd, kq, "", tout[0..4100]); }
if (tfd >= 0) {
var kdk : u32 = 0;
while (kdk < MAX_KEYS) {
client_set_key(tfd, setkey_byte(kq, kdk), "", tout[0..4100]);
kdk = kdk + 1;
}
}
} else if (kact == KA_CHECK) {
kbusy = kq;
kpend = 0;
Expand Down Expand Up @@ -3893,6 +3929,8 @@ pub fn main() -> void {
god = false;
dp = false;
keys_refresh(kstored[0..9], kmask[0..144], kmlen[0..9]);
// the refresh also hands the server every numbered key again (a server that restarted forgot them)
if (tfd >= 0) { keys_sync(tfd, kst[0..9], tout[0..4100]); }
const khn : usize = ns_bytes(home_dir(), khome[0..256]);
kemask = installed_mask(khome[0..khn]);
ktpick = term_pick(term_mask());
Expand Down Expand Up @@ -4130,8 +4168,8 @@ pub fn main() -> void {
}
}
// K. pre-flight (M12): once at start, check the stored key behind each claude profile, in the order
// keys.t27 PROFILE_PROVIDER gives, so the first pane already avoids a dead key
if (kbusy == KY_NONE and kpre < 2) {
// keys.t27 PROFILE_PROVIDER gives (all PROFILE_COUNT of them), so the first pane already avoids a dead key
if (kbusy == KY_NONE and kpre < PROFILE_COUNT) {
const kpp : u32 = PROFILE_PROVIDER[kpre];
kpre = kpre + 1;
if (probe_has(kpp) and kc_has(kpp)) {
Expand Down
Loading
Loading