@kubitnodes/stellaris 0.3.20 → 0.3.21
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.
- package/dist/cli/index.js +182 -56
- package/dist/server/index.js +281 -85
- package/package.json +1 -1
- package/web/dist/assets/BackLink-23Q1JvmZ.js +1 -0
- package/web/dist/assets/{ChannelForms-BYrcUldk.js → ChannelForms-0Cwh59E7.js} +1 -1
- package/web/dist/assets/{ChannelView-GJ_DusLQ.js → ChannelView-CqWV1zza.js} +2 -2
- package/web/dist/assets/{CitizenView-Cx7IYSRr.js → CitizenView-s5OdjXmB.js} +2 -2
- package/web/dist/assets/{CitizensView-IKH7_yNQ.js → CitizensView-r6SZLnVl.js} +1 -1
- package/web/dist/assets/{CliIcon-vSuwLv2K.js → CliIcon-BOSygXeI.js} +1 -1
- package/web/dist/assets/{CronsView-DJbi8Qhx.js → CronsView-Y-bAE0l2.js} +1 -1
- package/web/dist/assets/{KnowledgeView-CoYOG9tI.js → KnowledgeView-CI4PJWwm.js} +1 -1
- package/web/dist/assets/{Markdown-YC-fbHiE.js → Markdown-DyckB74r.js} +4 -4
- package/web/dist/assets/{MessageItem-D7MUCA_T.js → MessageItem-DI05jbrI.js} +1 -1
- package/web/dist/assets/{MetricsView-CE94VW0I.js → MetricsView-DKh-lEW5.js} +1 -1
- package/web/dist/assets/{NeedsYouView-Otnm3lcG.js → NeedsYouView-C6FfiTsg.js} +1 -1
- package/web/dist/assets/{Overview-B8f6rLb2.js → Overview-D5RvWBg5.js} +1 -1
- package/web/dist/assets/{Pane-_D7n5uKH.js → Pane-BendOaje.js} +1 -1
- package/web/dist/assets/{Picker-DUiiQMFN.js → Picker-BuVnakvi.js} +1 -1
- package/web/dist/assets/{ProjectView-D_ACOmEd.js → ProjectView-blBEh70W.js} +1 -1
- package/web/dist/assets/ProposalView-CMntpKTc.js +2 -0
- package/web/dist/assets/{ProposalsView-BovF6CMD.js → ProposalsView-CVUAV2Mm.js} +1 -1
- package/web/dist/assets/{RunnersView-B2_uCVHC.js → RunnersView-B-wuWVgB.js} +1 -1
- package/web/dist/assets/{SocietyView-Bf0iX8AY.js → SocietyView-CaVbfJWz.js} +1 -1
- package/web/dist/assets/{SubjectThread-B_Vmgs3d.js → SubjectThread-Bd_-sDhJ.js} +1 -1
- package/web/dist/assets/{TaskFileView-BzO0gjE6.js → TaskFileView-CO4Y97OT.js} +2 -2
- package/web/dist/assets/{TaskView-DB_Nji99.js → TaskView-DRZoHALo.js} +2 -2
- package/web/dist/assets/{TasksView-Bz36YYWq.js → TasksView-CQVQo3zF.js} +1 -1
- package/web/dist/assets/{ThreadForms-1mbMZsbv.js → ThreadForms-CUSeWu0c.js} +1 -1
- package/web/dist/assets/{ThreadItem-QJSA92C4.js → ThreadItem-BPYYXh3V.js} +1 -1
- package/web/dist/assets/{ThreadView-BECLqt5B.js → ThreadView-BcI2i6FI.js} +1 -1
- package/web/dist/assets/{citizen-DVFr5T6R.js → citizen-Eff1h9rA.js} +1 -1
- package/web/dist/assets/{compose-_6feqP1t.js → compose-D73Agshx.js} +1 -1
- package/web/dist/assets/{format-DxXtbdNM.js → format-fw0G_Vff.js} +1 -1
- package/web/dist/assets/governance-sRsfOw52.js +1 -0
- package/web/dist/assets/index-CYiok0pT.js +23 -0
- package/web/dist/assets/link-Cl7vKbOz.js +1 -0
- package/web/dist/assets/{tasks-B6I9DJ8O.js → tasks-CyXIxoAy.js} +1 -1
- package/web/dist/assets/useMatch-D-tEHQEw.js +1 -0
- package/web/dist/assets/{useStickyScroll-Ca7sWbGH.js → useStickyScroll-Bn4YyEXL.js} +1 -1
- package/web/dist/index.html +8 -8
- package/web/dist/assets/BackLink-BjfX2KZl.js +0 -1
- package/web/dist/assets/ProposalView-4d0U3nEb.js +0 -2
- package/web/dist/assets/governance-C9xH4QTm.js +0 -1
- package/web/dist/assets/index-BEVCPm4C.js +0 -23
- package/web/dist/assets/link-CTjFMNoY.js +0 -1
- package/web/dist/assets/useMatch-B3RzAe8y.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -146,7 +146,7 @@ var SEED_ROLES = [
|
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
name: "steward",
|
|
149
|
-
purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one. Never approves its own proposals; hiring, roles, and
|
|
149
|
+
purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one, by a reallocation of its replicas, backlog threshold, wakes, or verbs. Never approves its own proposals; hiring, roles, retirements, and reallocations that change a role's verbs or replicas are decided by the user, and channels, skills, and other reallocations the steward may decide. Curates society knowledge and skills.",
|
|
150
150
|
verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS, ...CURATING_VERBS],
|
|
151
151
|
wakeTriggers: ["ops_event", "heartbeat"],
|
|
152
152
|
maxReplicas: 1,
|
|
@@ -615,9 +615,54 @@ var ChannelProposalSchema = z4.object({
|
|
|
615
615
|
name: NameSchema,
|
|
616
616
|
purpose: z4.string().min(1)
|
|
617
617
|
});
|
|
618
|
+
var RoleChangesSchema = z4.strictObject({
|
|
619
|
+
maxReplicas: z4.number().int().min(1).optional(),
|
|
620
|
+
backlogThreshold: z4.number().positive().optional(),
|
|
621
|
+
wakeTriggers: z4.array(CharterTriggerSchema).optional(),
|
|
622
|
+
verbs: z4.array(VerbNameSchema).optional()
|
|
623
|
+
}).refine((changes) => Object.values(changes).some((value) => value !== void 0), {
|
|
624
|
+
message: "a reallocation changes at least one of maxReplicas, backlogThreshold, wakeTriggers, verbs"
|
|
625
|
+
});
|
|
618
626
|
var ReallocationProposalSchema = z4.object({
|
|
627
|
+
role: NameSchema,
|
|
628
|
+
changes: RoleChangesSchema,
|
|
619
629
|
description: z4.string().min(1)
|
|
620
630
|
});
|
|
631
|
+
var LegacyReallocationSchema = z4.object({ description: z4.string().min(1) });
|
|
632
|
+
function applyRoleChanges(role2, changes) {
|
|
633
|
+
return {
|
|
634
|
+
...role2,
|
|
635
|
+
...changes.maxReplicas === void 0 ? {} : { maxReplicas: changes.maxReplicas },
|
|
636
|
+
...changes.backlogThreshold === void 0 ? {} : { backlogThreshold: changes.backlogThreshold },
|
|
637
|
+
...changes.wakeTriggers === void 0 ? {} : { wakeTriggers: [...changes.wakeTriggers] },
|
|
638
|
+
...changes.verbs === void 0 ? {} : { verbs: [...changes.verbs] }
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function listedOrNone(values) {
|
|
642
|
+
return values.length === 0 ? "none" : values.join(", ");
|
|
643
|
+
}
|
|
644
|
+
function roleChangeLines(role2, changes) {
|
|
645
|
+
const lines = [];
|
|
646
|
+
if (changes.maxReplicas !== void 0 && changes.maxReplicas !== role2.maxReplicas) {
|
|
647
|
+
lines.push(`maxReplicas ${role2.maxReplicas} \u2192 ${changes.maxReplicas}`);
|
|
648
|
+
}
|
|
649
|
+
if (changes.backlogThreshold !== void 0 && changes.backlogThreshold !== role2.backlogThreshold) {
|
|
650
|
+
lines.push(`backlogThreshold ${role2.backlogThreshold} \u2192 ${changes.backlogThreshold}`);
|
|
651
|
+
}
|
|
652
|
+
const wakes = changes.wakeTriggers;
|
|
653
|
+
if (wakes !== void 0 && (wakes.length !== role2.wakeTriggers.length || wakes.some((trigger) => !role2.wakeTriggers.includes(trigger)))) {
|
|
654
|
+
lines.push(`wakeTriggers ${listedOrNone(role2.wakeTriggers)} \u2192 ${listedOrNone(wakes)}`);
|
|
655
|
+
}
|
|
656
|
+
if (changes.verbs !== void 0) {
|
|
657
|
+
const next = changes.verbs;
|
|
658
|
+
const added = next.filter((verb) => !role2.verbs.includes(verb)).map((verb) => `+${verb}`);
|
|
659
|
+
const removed = role2.verbs.filter((verb) => !next.includes(verb)).map((verb) => `\u2212${verb}`);
|
|
660
|
+
if (added.length + removed.length > 0) {
|
|
661
|
+
lines.push(`verbs ${[...added, ...removed].join(" ")}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return lines;
|
|
665
|
+
}
|
|
621
666
|
var RetirementProposalSchema = z4.object({
|
|
622
667
|
agent: NameSchema,
|
|
623
668
|
reason: z4.string().min(1)
|
|
@@ -649,6 +694,32 @@ var ROLE_KIND_APPROVERS = {
|
|
|
649
694
|
reallocation: [USER_ROLE, "steward"],
|
|
650
695
|
skill: [USER_ROLE, "steward"]
|
|
651
696
|
};
|
|
697
|
+
var USER_DECIDED_ROLE_CHANGES = ["verbs", "maxReplicas"];
|
|
698
|
+
function proposalApprovers(kind, charter) {
|
|
699
|
+
if (kind === "reallocation") {
|
|
700
|
+
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
701
|
+
if (parsed.success && USER_DECIDED_ROLE_CHANGES.some((field) => parsed.data.changes[field] !== void 0)) {
|
|
702
|
+
return [USER_ROLE];
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return ROLE_KIND_APPROVERS[kind];
|
|
706
|
+
}
|
|
707
|
+
function describeRoleChanges(changes) {
|
|
708
|
+
const parts = [];
|
|
709
|
+
if (changes.maxReplicas !== void 0) {
|
|
710
|
+
parts.push(`up to ${changes.maxReplicas} per project`);
|
|
711
|
+
}
|
|
712
|
+
if (changes.backlogThreshold !== void 0) {
|
|
713
|
+
parts.push(`backlog threshold ${changes.backlogThreshold}`);
|
|
714
|
+
}
|
|
715
|
+
if (changes.wakeTriggers !== void 0) {
|
|
716
|
+
parts.push(changes.wakeTriggers.length === 0 ? "no ambient wakes" : `wakes on ${changes.wakeTriggers.join(", ")}`);
|
|
717
|
+
}
|
|
718
|
+
if (changes.verbs !== void 0) {
|
|
719
|
+
parts.push(`${changes.verbs.length} verbs`);
|
|
720
|
+
}
|
|
721
|
+
return parts.join(", ");
|
|
722
|
+
}
|
|
652
723
|
function describeCharter(kind, charter) {
|
|
653
724
|
switch (kind) {
|
|
654
725
|
case "member": {
|
|
@@ -680,9 +751,14 @@ function describeCharter(kind, charter) {
|
|
|
680
751
|
}
|
|
681
752
|
case "reallocation": {
|
|
682
753
|
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
683
|
-
if (
|
|
754
|
+
if (parsed.success) {
|
|
755
|
+
const { role: role2, changes, description } = parsed.data;
|
|
756
|
+
return `reallocation of role ${role2} (${describeRoleChanges(changes)}): ${description}`;
|
|
757
|
+
}
|
|
758
|
+
const legacy = LegacyReallocationSchema.safeParse(charter);
|
|
759
|
+
if (!legacy.success)
|
|
684
760
|
break;
|
|
685
|
-
return `reallocation: ${
|
|
761
|
+
return `reallocation: ${legacy.data.description}`;
|
|
686
762
|
}
|
|
687
763
|
case "skill": {
|
|
688
764
|
const parsed = SkillProposalSchema.safeParse(charter);
|
|
@@ -1740,7 +1816,7 @@ var RunnerConfigSchema = z11.object({
|
|
|
1740
1816
|
});
|
|
1741
1817
|
|
|
1742
1818
|
// packages/shared/dist/version.js
|
|
1743
|
-
var STELLARIS_VERSION = "0.3.
|
|
1819
|
+
var STELLARIS_VERSION = "0.3.21";
|
|
1744
1820
|
|
|
1745
1821
|
// packages/board-core/dist/errors.js
|
|
1746
1822
|
var BoardError = class extends Error {
|
|
@@ -4474,18 +4550,16 @@ ${DASHBOARD_LINE}
|
|
|
4474
4550
|
async readDigest(actor, input = {}) {
|
|
4475
4551
|
const args = VerbInputs.read_inbox.parse(input);
|
|
4476
4552
|
await this.authorize(actor, "read_inbox");
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
return { messages: filed.map((entry) => entry.message), cursor };
|
|
4488
|
-
});
|
|
4553
|
+
const { cursors: stored, upto } = await this.digestSnapshot(actor.name);
|
|
4554
|
+
const from = args.since_cursor === void 0 ? stored : { ...NO_CURSORS, digest: args.since_cursor };
|
|
4555
|
+
const conversation = actor.scope === void 0 ? void 0 : { scope: actor.scope, part: conversationPart(actor) ?? null };
|
|
4556
|
+
const filed = (await this.digestSince(actor, from, upto, conversation)).slice(0, args.limit);
|
|
4557
|
+
if (args.advance) {
|
|
4558
|
+
await this.mutex.run(() => this.advanceCursors(actor.name, filed));
|
|
4559
|
+
}
|
|
4560
|
+
const last = filed.at(-1)?.message.id;
|
|
4561
|
+
const cursor = last ?? (conversation === void 0 ? from.digest : cursorOf(from, conversation.scope, conversation.part));
|
|
4562
|
+
return { messages: filed.map((entry) => entry.message), cursor };
|
|
4489
4563
|
}
|
|
4490
4564
|
/**
|
|
4491
4565
|
* How many unread digest messages a member has in each conversation: a scope's home, for its
|
|
@@ -4497,28 +4571,39 @@ ${DASHBOARD_LINE}
|
|
|
4497
4571
|
* whose signals that call for action wake their readers directly.
|
|
4498
4572
|
*/
|
|
4499
4573
|
async unreadByConversation(actor) {
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
}
|
|
4507
|
-
const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
|
|
4508
|
-
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4509
|
-
continue;
|
|
4510
|
-
}
|
|
4511
|
-
const key = conversationKey(scope, part);
|
|
4512
|
-
const entry = counts.get(key) ?? {
|
|
4513
|
-
scope,
|
|
4514
|
-
thread: threadOfPart(part) ?? null,
|
|
4515
|
-
channel: channelOfPart(part) ?? null,
|
|
4516
|
-
count: 0
|
|
4517
|
-
};
|
|
4518
|
-
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
4574
|
+
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
4575
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4576
|
+
const unscoped = { name: actor.name, role: actor.role };
|
|
4577
|
+
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
4578
|
+
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
4579
|
+
continue;
|
|
4519
4580
|
}
|
|
4520
|
-
|
|
4521
|
-
|
|
4581
|
+
const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
|
|
4582
|
+
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4583
|
+
continue;
|
|
4584
|
+
}
|
|
4585
|
+
const key = conversationKey(scope, part);
|
|
4586
|
+
const entry = counts.get(key) ?? {
|
|
4587
|
+
scope,
|
|
4588
|
+
thread: threadOfPart(part) ?? null,
|
|
4589
|
+
channel: channelOfPart(part) ?? null,
|
|
4590
|
+
count: 0
|
|
4591
|
+
};
|
|
4592
|
+
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
4593
|
+
}
|
|
4594
|
+
return [...counts.values()];
|
|
4595
|
+
}
|
|
4596
|
+
/**
|
|
4597
|
+
* A reader's cursors and the id below which every message is already on disk, taken under the
|
|
4598
|
+
* mutex, which a digest scan then leaves: messages carry ids from the same monotonic source and are
|
|
4599
|
+
* written under the mutex, so the scan, which counts nothing newer than the mark, cannot pass over
|
|
4600
|
+
* one written while it reads, and a post never waits for a scan.
|
|
4601
|
+
*/
|
|
4602
|
+
digestSnapshot(agent2) {
|
|
4603
|
+
return this.mutex.run(async () => ({
|
|
4604
|
+
cursors: await this.readCursors(agent2),
|
|
4605
|
+
upto: this.newId()
|
|
4606
|
+
}));
|
|
4522
4607
|
}
|
|
4523
4608
|
async readCursors(agent2) {
|
|
4524
4609
|
const file = this.paths.agentCursors(agent2);
|
|
@@ -4560,9 +4645,10 @@ ${DASHBOARD_LINE}
|
|
|
4560
4645
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
4561
4646
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
4562
4647
|
* top-level messages; for a scope's home, general's and those of a project the member is not in,
|
|
4563
|
-
* since each thread and each other channel is a conversation of its own.
|
|
4648
|
+
* since each thread and each other channel is a conversation of its own. Nothing newer than
|
|
4649
|
+
* `upto` counts (see `digestSnapshot`).
|
|
4564
4650
|
*/
|
|
4565
|
-
async digestSince(actor, cursors, conversation) {
|
|
4651
|
+
async digestSince(actor, cursors, upto, conversation) {
|
|
4566
4652
|
const conversationThread = threadOfPart(conversation?.part);
|
|
4567
4653
|
if (conversation !== void 0 && conversationThread !== void 0) {
|
|
4568
4654
|
const { scope, part } = conversation;
|
|
@@ -4570,7 +4656,7 @@ ${DASHBOARD_LINE}
|
|
|
4570
4656
|
if (found === null) {
|
|
4571
4657
|
return [];
|
|
4572
4658
|
}
|
|
4573
|
-
const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part));
|
|
4659
|
+
const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part), upto);
|
|
4574
4660
|
return messages.filter((message) => message.author !== actor.name).map((message) => ({ scope, part, message })).toSorted(byId);
|
|
4575
4661
|
}
|
|
4576
4662
|
const agent2 = await this.readAgent(actor.name);
|
|
@@ -4588,7 +4674,7 @@ ${DASHBOARD_LINE}
|
|
|
4588
4674
|
];
|
|
4589
4675
|
const known = starts.filter((start) => start !== null);
|
|
4590
4676
|
const since = known.length < starts.length ? null : known.toSorted((a, b) => a < b ? -1 : a > b ? 1 : 0)[0] ?? null;
|
|
4591
|
-
for await (const message of this.iterateMessages(since)) {
|
|
4677
|
+
for await (const message of this.iterateMessages(since, void 0, upto)) {
|
|
4592
4678
|
const filedIn = wakeScope(agent2, parseChannelRef(message.channel).project);
|
|
4593
4679
|
const thread2 = message.thread ?? null;
|
|
4594
4680
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
@@ -5712,6 +5798,9 @@ ${args.summary}`);
|
|
|
5712
5798
|
}
|
|
5713
5799
|
if (args.blocked_by !== void 0) {
|
|
5714
5800
|
for (const id of args.blocked_by) {
|
|
5801
|
+
if (id === current.id) {
|
|
5802
|
+
throw new BoardError("VALIDATION", `task ${current.id} cannot be blocked by itself`);
|
|
5803
|
+
}
|
|
5715
5804
|
await this.findTask(id);
|
|
5716
5805
|
}
|
|
5717
5806
|
next = { ...next, blockedBy: [...args.blocked_by] };
|
|
@@ -5748,6 +5837,7 @@ ${args.summary}`);
|
|
|
5748
5837
|
from: current.status,
|
|
5749
5838
|
to: task2.status,
|
|
5750
5839
|
note: args.note ?? null,
|
|
5840
|
+
...args.blocked_by === void 0 ? {} : { blockedBy: task2.blockedBy, previousBlockedBy: current.blockedBy },
|
|
5751
5841
|
...args.pull_request === void 0 ? {} : { pullRequest: task2.pullRequest?.url ?? null }
|
|
5752
5842
|
});
|
|
5753
5843
|
}
|
|
@@ -7046,12 +7136,37 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7046
7136
|
case "archive":
|
|
7047
7137
|
await this.validateArchive(ArchiveProposalSchema.parse(charter).project);
|
|
7048
7138
|
return;
|
|
7049
|
-
case "reallocation":
|
|
7139
|
+
case "reallocation": {
|
|
7140
|
+
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
7141
|
+
if (parsed.success) {
|
|
7142
|
+
await this.reallocatedRole(parsed.data);
|
|
7143
|
+
} else {
|
|
7144
|
+
LegacyReallocationSchema.parse(charter);
|
|
7145
|
+
}
|
|
7050
7146
|
return;
|
|
7147
|
+
}
|
|
7051
7148
|
default:
|
|
7052
7149
|
return;
|
|
7053
7150
|
}
|
|
7054
7151
|
}
|
|
7152
|
+
/**
|
|
7153
|
+
* The role as a reallocation would leave it, with what changes: refused for the user's charter,
|
|
7154
|
+
* a role that does not exist, a charter the changes would make invalid, and changes the role
|
|
7155
|
+
* has already.
|
|
7156
|
+
*/
|
|
7157
|
+
async reallocatedRole(reallocation) {
|
|
7158
|
+
if (reallocation.role === USER_ROLE) {
|
|
7159
|
+
throw new BoardError("FORBIDDEN", "the user charter is not subject to proposals");
|
|
7160
|
+
}
|
|
7161
|
+
const current = await this.readRole(reallocation.role);
|
|
7162
|
+
const next = RoleCharterSchema.parse(applyRoleChanges(current, reallocation.changes));
|
|
7163
|
+
await this.validateRole(next);
|
|
7164
|
+
const changed = roleChangeLines(current, reallocation.changes);
|
|
7165
|
+
if (changed.length === 0) {
|
|
7166
|
+
throw new BoardError("VALIDATION", `role ${current.name} has these values already; the reallocation changes nothing`);
|
|
7167
|
+
}
|
|
7168
|
+
return { next, changed };
|
|
7169
|
+
}
|
|
7055
7170
|
async executeProvision(by, proposal2) {
|
|
7056
7171
|
const meta = { proposalId: proposal2.id };
|
|
7057
7172
|
switch (proposal2.kind) {
|
|
@@ -7088,8 +7203,15 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7088
7203
|
const { members } = await this.archiveUnlocked(by, input.project, input.reason, meta);
|
|
7089
7204
|
return { project: input.project, members };
|
|
7090
7205
|
}
|
|
7091
|
-
case "reallocation":
|
|
7092
|
-
|
|
7206
|
+
case "reallocation": {
|
|
7207
|
+
const parsed = ReallocationProposalSchema.safeParse(proposal2.charter);
|
|
7208
|
+
if (!parsed.success) {
|
|
7209
|
+
return void 0;
|
|
7210
|
+
}
|
|
7211
|
+
const { next, changed } = await this.reallocatedRole(parsed.data);
|
|
7212
|
+
await this.writeRoleUnlocked(by, next, meta);
|
|
7213
|
+
return { role: next.name, changed };
|
|
7214
|
+
}
|
|
7093
7215
|
default:
|
|
7094
7216
|
return void 0;
|
|
7095
7217
|
}
|
|
@@ -7789,7 +7911,7 @@ ${charter.purpose}
|
|
|
7789
7911
|
if (filed.proposedBy === actor.name) {
|
|
7790
7912
|
throw new BoardError("FORBIDDEN", "a proposer never decides its own proposal");
|
|
7791
7913
|
}
|
|
7792
|
-
const approvers =
|
|
7914
|
+
const approvers = proposalApprovers(filed.kind, filed.charter);
|
|
7793
7915
|
if (!approvers.includes(actor.role)) {
|
|
7794
7916
|
throw new BoardError("FORBIDDEN", `a ${filed.kind} proposal requires one of: ${approvers.join(", ")}`);
|
|
7795
7917
|
}
|
|
@@ -7872,7 +7994,7 @@ ${charter.purpose}
|
|
|
7872
7994
|
}
|
|
7873
7995
|
if (subject?.kind === "proposal") {
|
|
7874
7996
|
const proposal2 = await this.readProposal(subject.id);
|
|
7875
|
-
if (proposal2.proposedBy === actor.name || proposal2.decidedBy === actor.name ||
|
|
7997
|
+
if (proposal2.proposedBy === actor.name || proposal2.decidedBy === actor.name || proposalApprovers(proposal2.kind, proposal2.charter).includes(actor.role)) {
|
|
7876
7998
|
return true;
|
|
7877
7999
|
}
|
|
7878
8000
|
return (await this.readRole(actor.role)).wakeTriggers.includes(OPS_WAKE_TRIGGER);
|
|
@@ -7929,11 +8051,12 @@ ${charter.purpose}
|
|
|
7929
8051
|
});
|
|
7930
8052
|
await this.endCronsUnlocked(by, `its thread closed: its ${found.thread.subject?.kind ?? "subject"} was ${ended}`, (cron2) => cron2.thread === subjectId);
|
|
7931
8053
|
}
|
|
7932
|
-
|
|
8054
|
+
/** The messages in a folder after `since` and, when given, up to `upto`, read by name first. */
|
|
8055
|
+
async readMessagesIn(dir, since, upto) {
|
|
7933
8056
|
const messages = [];
|
|
7934
8057
|
for (const file of await listFiles(dir)) {
|
|
7935
8058
|
const id = file.slice(0, 26);
|
|
7936
|
-
if (since !== null && id <= since) {
|
|
8059
|
+
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
7937
8060
|
continue;
|
|
7938
8061
|
}
|
|
7939
8062
|
const doc = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
|
|
@@ -7941,20 +8064,23 @@ ${charter.purpose}
|
|
|
7941
8064
|
}
|
|
7942
8065
|
return messages;
|
|
7943
8066
|
}
|
|
7944
|
-
/**
|
|
7945
|
-
|
|
8067
|
+
/**
|
|
8068
|
+
* Every message newer than `since`, and no newer than `upto` when given, across society
|
|
8069
|
+
* channels, project channels, and threads.
|
|
8070
|
+
*/
|
|
8071
|
+
async *iterateMessages(since, project2, upto) {
|
|
7946
8072
|
if (project2 === void 0) {
|
|
7947
8073
|
for (const channel of await listDirs(this.paths.societyChannels())) {
|
|
7948
|
-
yield* await this.readMessagesIn(this.paths.societyChannel(channel), since);
|
|
8074
|
+
yield* await this.readMessagesIn(this.paths.societyChannel(channel), since, upto);
|
|
7949
8075
|
}
|
|
7950
|
-
yield* this.threadMessagesIn(this.paths.societyThreads(), since);
|
|
8076
|
+
yield* this.threadMessagesIn(this.paths.societyThreads(), since, upto);
|
|
7951
8077
|
}
|
|
7952
8078
|
const projects = project2 === void 0 ? await listDirs(this.paths.projects()) : [project2];
|
|
7953
8079
|
for (const slug of projects) {
|
|
7954
8080
|
for (const channel of await listDirs(this.paths.projectChannels(slug))) {
|
|
7955
|
-
yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since);
|
|
8081
|
+
yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since, upto);
|
|
7956
8082
|
}
|
|
7957
|
-
yield* this.threadMessagesIn(this.paths.threads(slug), since);
|
|
8083
|
+
yield* this.threadMessagesIn(this.paths.threads(slug), since, upto);
|
|
7958
8084
|
}
|
|
7959
8085
|
}
|
|
7960
8086
|
/** Every folder that holds messages: each channel's and each thread's, in the society and every project. */
|
|
@@ -7974,9 +8100,9 @@ ${charter.purpose}
|
|
|
7974
8100
|
}
|
|
7975
8101
|
}
|
|
7976
8102
|
}
|
|
7977
|
-
async *threadMessagesIn(threads, since) {
|
|
8103
|
+
async *threadMessagesIn(threads, since, upto) {
|
|
7978
8104
|
for (const id of await listDirs(threads)) {
|
|
7979
|
-
yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since);
|
|
8105
|
+
yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since, upto);
|
|
7980
8106
|
}
|
|
7981
8107
|
}
|
|
7982
8108
|
};
|