feat: parliament daemon, login gate, peer context sync (0.9.13)

Login claims the chair, writes a session ticket, and starts a background
service for discovery plus revision-based context sync. Password Realm
opens require --login (or CST_REALM_PASSWORD); --service/--service-stop
manage the resident agent.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-28 23:11:23 +08:00
co-authored by Cursor
parent de9ee28773
commit e853abac5e
13 changed files with 769 additions and 12 deletions
+114 -1
View File
@@ -165,7 +165,7 @@ int cst_parliament_claim_chair(const char *instance_id,
return cst_parliament_save(&m);
}
int cst_parliament_context_put(const char *text) {
static int context_write_file(const char *text) {
char path[OS_PATH_MAX], tmp[OS_PATH_MAX + 8];
if (context_path(path, sizeof(path)) != 0)
return -1;
@@ -185,6 +185,12 @@ int cst_parliament_context_put(const char *text) {
remove(tmp);
return -1;
}
return 0;
}
int cst_parliament_context_put(const char *text) {
if (context_write_file(text) != 0)
return -1;
cst_parliament_meta_t m;
if (cst_parliament_load(&m) == 0) {
m.revision++;
@@ -290,8 +296,115 @@ char *cst_parliament_login(const char *password, const char *instance_id,
cst_buf_puts(&hand, ctx ? ctx : "");
free(ctx);
free(dev);
if (cst_parliament_session_write(instance_id) != 0) {
if (err && err_sz)
snprintf(err, err_sz, "session ticket write failed");
crypto_wipe(keys.seed, sizeof(keys.seed));
crypto_wipe(keys.sk, sizeof(keys.sk));
crypto_wipe(sk, sizeof(sk));
cst_buf_free(&hand);
return NULL;
}
crypto_wipe(keys.seed, sizeof(keys.seed));
crypto_wipe(keys.sk, sizeof(keys.sk));
crypto_wipe(sk, sizeof(sk));
return cst_buf_steal(&hand);
}
static int session_path(char *out, size_t n) {
char dir[OS_PATH_MAX];
if (cst_realm_dir(dir, sizeof(dir)) != 0)
return -1;
return os_path_join(out, n, dir, "chair.session");
}
int cst_parliament_session_write(const char *instance_id) {
if (!instance_id || !instance_id[0])
return -1;
char path[OS_PATH_MAX], tmp[OS_PATH_MAX + 8];
if (session_path(path, sizeof(path)) != 0)
return -1;
if (snprintf(tmp, sizeof(tmp), "%s.tmp", path) >= (int)sizeof(tmp))
return -1;
FILE *f = fopen(tmp, "wb");
if (!f)
return -1;
int ok = fprintf(f, "instance=%s\nissued=%ld\n", instance_id,
(long)time(NULL)) > 0;
if (fclose(f) != 0)
ok = 0;
if (!ok) {
remove(tmp);
return -1;
}
if (os_rename_replace(tmp, path) != 0) {
remove(tmp);
return -1;
}
return 0;
}
int cst_parliament_session_ok(char *instance_id_out, size_t n) {
char path[OS_PATH_MAX];
if (session_path(path, sizeof(path)) != 0)
return 0;
FILE *f = fopen(path, "rb");
if (!f)
return 0;
char line[256], iid[64] = {0};
long issued = 0;
while (fgets(line, sizeof(line), f)) {
if (!strncmp(line, "instance=", 9)) {
const char *p = line + 9;
size_t i = 0;
while (p[i] && p[i] != '\n' && p[i] != '\r' && i + 1 < sizeof(iid)) {
iid[i] = p[i];
i++;
}
iid[i] = 0;
} else if (!strncmp(line, "issued=", 7))
issued = atol(line + 7);
}
fclose(f);
if (!iid[0] || issued <= 0)
return 0;
/* Ticket valid for 24h of wall clock. */
long now = (long)time(NULL);
if (now < issued || now - issued > 86400)
return 0;
cst_parliament_meta_t m;
if (cst_parliament_load(&m) != 0)
return 0;
if (!m.chair_instance[0] || strcmp(m.chair_instance, iid) != 0)
return 0;
if (instance_id_out && n)
snprintf(instance_id_out, n, "%s", iid);
return 1;
}
void cst_parliament_session_clear(void) {
char path[OS_PATH_MAX];
if (session_path(path, sizeof(path)) == 0)
remove(path);
}
int cst_parliament_apply_sync(unsigned long revision, const char *parliament_id,
const char *project_id, const char *chair,
const char *context) {
cst_parliament_meta_t m;
if (cst_parliament_load(&m) != 0)
return -1;
if (revision <= m.revision)
return 0;
if (parliament_id && parliament_id[0])
snprintf(m.parliament_id, sizeof(m.parliament_id), "%s", parliament_id);
if (project_id)
snprintf(m.project_id, sizeof(m.project_id), "%s", project_id);
if (chair)
snprintf(m.chair_instance, sizeof(m.chair_instance), "%s", chair);
m.revision = revision;
snprintf(m.status, sizeof(m.status), "%s", "open");
if (context_write_file(context ? context : "") != 0)
return -1;
return cst_parliament_save(&m);
}