feat: parliament seat collaboration loop (0.9.19)

Seats publish via parliament_note, sync pushes on write, member join without
stealing the chair, and worker tools are hard-gated when parliament is active.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-29 11:29:38 +08:00
co-authored by Cursor
parent 1605d74f17
commit 812e3ee265
15 changed files with 532 additions and 83 deletions
+44 -5
View File
@@ -30,12 +30,18 @@ int main(void) {
err, sizeof(err));
assert(hand && strstr(hand, "LOGIN_OK") && strstr(hand, "chair=phone-1"));
free(hand);
assert(cst_parliament_is_chair());
assert(cst_parliament_sync_pending()); /* login bumps context path request */
cst_parliament_meta_t m;
assert(cst_parliament_load(&m) == 0);
assert(!strcmp(m.chair_instance, "phone-1"));
assert(cst_parliament_context_put("shared agenda v1") == 0);
assert(cst_parliament_sync_pending());
cst_parliament_sync_ack();
assert(!cst_parliament_sync_pending());
hand = cst_parliament_login("correct horse battery", "laptop-2", "thinkpad",
err, sizeof(err));
assert(hand && strstr(hand, "handover_from=phone-1"));
@@ -43,11 +49,37 @@ int main(void) {
free(hand);
assert(cst_parliament_load(&m) == 0);
assert(!strcmp(m.chair_instance, "laptop-2"));
assert(cst_parliament_is_chair());
assert(cst_parliament_session_ok(NULL, 0));
assert(cst_parliament_new_project("proj-b") == 0);
/* Member seat can join without stealing the chair. */
hand = cst_parliament_seat_join("correct horse battery", "tablet-3", "pad",
err, sizeof(err));
assert(hand && strstr(hand, "JOIN_OK") && strstr(hand, "role=member"));
assert(strstr(hand, "chair=laptop-2"));
free(hand);
/* Join rewrote the local seat ticket to tablet-3 (this process). */
{
char sid[64] = {0};
assert(cst_parliament_session_ok(sid, sizeof(sid)));
assert(!strcmp(sid, "tablet-3"));
assert(!cst_parliament_is_chair());
}
assert(cst_parliament_load(&m) == 0);
assert(!strcmp(m.chair_instance, "laptop-2"));
assert(cst_parliament_context_append("tablet-3", "status",
"ready to take the UI slice") == 0);
char *ctx = cst_parliament_context_get();
assert(ctx && strstr(ctx, "shared agenda v1"));
assert(strstr(ctx, "seat=tablet-3") && strstr(ctx, "ready to take the UI"));
free(ctx);
assert(cst_parliament_sync_pending());
cst_parliament_sync_ack();
assert(cst_parliament_new_project("proj-b") == 0);
ctx = cst_parliament_context_get();
assert(ctx && ctx[0] == 0);
free(ctx);
assert(cst_parliament_load(&m) == 0);
@@ -55,9 +87,11 @@ int main(void) {
assert(m.chair_instance[0] == 0);
assert(!cst_parliament_session_ok(NULL, 0));
assert(cst_parliament_session_write("local-a") == 0);
/* Claim chair so session_ok matches. */
assert(cst_parliament_claim_chair("local-a", "dev") == 0);
hand = cst_parliament_login("correct horse battery", "local-a", "dev", err,
sizeof(err));
assert(hand && strstr(hand, "LOGIN_OK"));
free(hand);
assert(cst_parliament_is_chair());
assert(cst_parliament_session_ok(NULL, 0));
assert(cst_parliament_apply_sync(9, "parl-x", "proj-c", "chair-z",
"synced-body") == 0);
@@ -67,17 +101,22 @@ int main(void) {
assert(cst_parliament_load(&m) == 0);
assert(m.revision == 9);
assert(!strcmp(m.chair_instance, "chair-z"));
/* Remote chair takeover clears local login ticket. */
/* Remote chair takeover clears the demoted chair's local ticket. */
assert(!cst_parliament_session_ok(NULL, 0));
char *st = cst_parliament_status_text();
assert(st && strstr(st, "parliament"));
assert(strstr(st, "service"));
assert(strstr(st, "peer_sync") && strstr(st, "sealed"));
assert(strstr(st, "role"));
free(st);
assert(cst_parliament_is_active());
assert(cst_parliament_daemon_info(NULL, NULL, 0, NULL) != 0);
char def[64];
assert(cst_parliament_default_instance(def, sizeof(def)) == 0);
assert(def[0]);
test_rmrf(dir);
puts("test_parliament: OK");
return 0;
+3 -3
View File
@@ -138,11 +138,11 @@ int main(void) {
}
cJSON *all = cst_tools_schema_cfg(&cfg);
/* Every tool the host can actually run is offered in the default profile;
* one whose platform probe fails (the clipboard without a helper) is not. */
/* Every tool active for this config is offered; platform-unavailable tools
* and mode-gated tools (e.g. parliament_note outside parliament) are not. */
int usable = 0;
for (int i = 0; i < cst_tool_count(); i++)
if (cst_tool_available(cst_tool_at(i), NULL, 0) == 0)
if (cst_tool_active(&cfg, cst_tool_at(i)))
usable++;
EXPECT(cJSON_GetArraySize(all) == usable, "default: every usable tool");
EXPECT(schema_has(all, "edit_file") && schema_has(all, "grep") &&