@idleware/ringi 0.6.0 → 0.7.0
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/README.md +7 -18
- package/package.json +1 -1
- package/src/main/resources/profiles/a2a-1.json +0 -4
- package/src/main/resources/skill/ringi/SKILL.md +2 -18
- package/target/dts/adapters/host-posix/index.d.ts +2 -2
- package/target/dts/adapters/transport-git/branches.d.ts +2 -3
- package/target/dts/adapters/transport-git/index.d.ts +0 -1
- package/target/dts/adapters/transport-git/remote/types.d.ts +0 -1
- package/target/dts/adapters/transport-git/repo.d.ts +0 -1
- package/target/dts/clients/cli/agent/index.d.ts +0 -2
- package/target/dts/clients/cli/shared/index.d.ts +0 -1
- package/target/dts/core/agents/heartbeat/index.d.ts +2 -5
- package/target/dts/core/agents/heartbeat/pulse.d.ts +1 -3
- package/target/dts/core/agents/overview.d.ts +1 -5
- package/target/dts/core/agents/ports/host.d.ts +5 -37
- package/target/dts/core/agents/sessions/agent.d.ts +0 -17
- package/target/dts/core/agents/sessions/index.d.ts +0 -1
- package/target/dts/core/hub/history/index.d.ts +1 -11
- package/target/dts/core/hub/ports/store.d.ts +1 -5
- package/target/dts/core/hub/protocol/model.d.ts +0 -1
- package/target/dts/core/hub/protocol/reading.d.ts +0 -1
- package/target/dts/core/hub/rooms/index.d.ts +0 -4
- package/target/dts/core/hub/rooms/messages.d.ts +0 -1
- package/target/dts/core/hub/rooms/rooms.d.ts +1 -1
- package/target/dts/core/hub/signals/model.d.ts +0 -2
- package/target/dts/core/hub/usecases/hubs.d.ts +0 -1
- package/target/dts/core/hub/usecases/room.d.ts +0 -1
- package/target/dts/core/hub/usecases/rooms.d.ts +1 -5
- package/target/dts/core/hub/usecases/send.d.ts +0 -1
- package/target/dts/core/hub/usecases/view.d.ts +0 -22
- package/target/dts/core/hub/work/index.d.ts +0 -1
- package/target/dts/index.d.ts +0 -1
- package/target/esm/adapters/host-posix/index.js +30 -32
- package/target/esm/adapters/transport-git/branches.js +13 -20
- package/target/esm/adapters/transport-git/index.js +1 -6
- package/target/esm/adapters/transport-git/marks.js +1 -1
- package/target/esm/adapters/transport-git/remote/admit.js +0 -2
- package/target/esm/adapters/transport-git/remote/heartbeat-push.js +1 -1
- package/target/esm/adapters/transport-git/remote/main.js +1 -4
- package/target/esm/adapters/transport-git/repo.js +0 -1
- package/target/esm/clients/cli/agent/index.js +4 -10
- package/target/esm/clients/cli/arg-parse.js +1 -1
- package/target/esm/clients/cli/commands.js +6 -7
- package/target/esm/clients/cli/help.js +6 -13
- package/target/esm/clients/cli/operator/heartbeat-format.js +1 -1
- package/target/esm/clients/cli/operator/hub-commands.js +1 -1
- package/target/esm/clients/cli/operator/index.js +1 -1
- package/target/esm/clients/cli/operator/room.js +46 -7
- package/target/esm/clients/cli/operator/where-format.js +1 -3
- package/target/esm/clients/cli/shared/index.js +1 -2
- package/target/esm/clients/cli/shared/messages.js +1 -1
- package/target/esm/clients/cli/shared/profile.js +2 -2
- package/target/esm/clients/cli/shared/work.js +1 -1
- package/target/esm/composition/index.js +1 -3
- package/target/esm/core/agents/heartbeat/index.js +16 -23
- package/target/esm/core/agents/heartbeat/pulse.js +18 -33
- package/target/esm/core/agents/heartbeat/tools.js +1 -2
- package/target/esm/core/agents/overview.js +5 -6
- package/target/esm/core/agents/ports/host.js +2 -2
- package/target/esm/core/agents/sessions/agent.js +3 -23
- package/target/esm/core/agents/sessions/binding.js +5 -7
- package/target/esm/core/agents/sessions/index.js +3 -7
- package/target/esm/core/agents/sessions/join.js +1 -5
- package/target/esm/core/agents/sessions/resume.js +3 -4
- package/target/esm/core/agents/sessions/wait.js +10 -31
- package/target/esm/core/hub/capabilities.js +4 -2
- package/target/esm/core/hub/history/index.js +3 -1
- package/target/esm/core/hub/ports/store.js +3 -3
- package/target/esm/core/hub/protocol/index.js +3 -3
- package/target/esm/core/hub/protocol/model.js +2 -3
- package/target/esm/core/hub/protocol/reading.js +1 -3
- package/target/esm/core/hub/rooms/compat.js +11 -3
- package/target/esm/core/hub/rooms/hubs.js +2 -5
- package/target/esm/core/hub/rooms/index.js +3 -3
- package/target/esm/core/hub/rooms/messages.js +5 -10
- package/target/esm/core/hub/rooms/rooms.js +9 -38
- package/target/esm/core/hub/signals/effective.js +1 -21
- package/target/esm/core/hub/signals/index.js +2 -2
- package/target/esm/core/hub/signals/pins.js +1 -1
- package/target/esm/core/hub/usecases/hubs.js +0 -3
- package/target/esm/core/hub/usecases/room.js +2 -3
- package/target/esm/core/hub/usecases/rooms.js +4 -17
- package/target/esm/core/hub/usecases/roster.js +1 -1
- package/target/esm/core/hub/usecases/send.js +2 -12
- package/target/esm/core/hub/usecases/view.js +2 -6
- package/target/esm/core/hub/work/task.js +4 -11
- package/target/esm/index.js +1 -2
- package/target/esm/lib/json.js +1 -1
- package/target/dts/adapters/host-posix/group-state.d.ts +0 -7
- package/target/dts/adapters/host-posix/groups.d.ts +0 -1
- package/target/dts/adapters/host-posix/launch.d.ts +0 -7
- package/target/dts/adapters/host-posix/probes.d.ts +0 -5
- package/target/dts/adapters/host-posix/stats.d.ts +0 -9
- package/target/dts/adapters/transport-kafka/index.d.ts +0 -2
- package/target/dts/adapters/transport-temporal/index.d.ts +0 -2
- package/target/dts/clients/cli/operator/room-lifecycle.d.ts +0 -5
- package/target/dts/clients/cli/operator/room-list.d.ts +0 -5
- package/target/dts/clients/cli/operator/room-members.d.ts +0 -4
- package/target/dts/clients/cli/operator/watch.d.ts +0 -2
- package/target/dts/clients/cli/shared/budget.d.ts +0 -7
- package/target/dts/core/agents/turns/budgets.d.ts +0 -5
- package/target/dts/core/agents/turns/dispatch.d.ts +0 -9
- package/target/dts/core/agents/turns/exhaustion.d.ts +0 -11
- package/target/dts/core/agents/turns/grants.d.ts +0 -4
- package/target/dts/core/agents/turns/index.d.ts +0 -4
- package/target/dts/core/agents/turns/limits.d.ts +0 -2
- package/target/dts/core/agents/turns/messages.d.ts +0 -10
- package/target/dts/core/agents/turns/pings.d.ts +0 -5
- package/target/dts/core/agents/turns/plan.d.ts +0 -3
- package/target/dts/core/agents/turns/poll.d.ts +0 -8
- package/target/dts/core/agents/turns/results.d.ts +0 -3
- package/target/dts/core/agents/turns/seat.d.ts +0 -2
- package/target/dts/core/agents/turns/types.d.ts +0 -59
- package/target/dts/core/hub/decisions/budget.d.ts +0 -15
- package/target/dts/core/hub/decisions/check.d.ts +0 -6
- package/target/dts/core/hub/decisions/index.d.ts +0 -5
- package/target/dts/core/hub/decisions/proposal.d.ts +0 -38
- package/target/dts/core/hub/decisions/usage.d.ts +0 -12
- package/target/dts/core/hub/usecases/budget.d.ts +0 -23
- package/target/dts/core/hub/usecases/propose.d.ts +0 -17
- package/target/esm/adapters/host-posix/group-state.js +0 -24
- package/target/esm/adapters/host-posix/groups.js +0 -43
- package/target/esm/adapters/host-posix/launch.js +0 -80
- package/target/esm/adapters/host-posix/probes.js +0 -66
- package/target/esm/adapters/host-posix/stats.js +0 -27
- package/target/esm/adapters/transport-kafka/index.js +0 -4
- package/target/esm/adapters/transport-temporal/index.js +0 -4
- package/target/esm/clients/cli/operator/room-lifecycle.js +0 -23
- package/target/esm/clients/cli/operator/room-list.js +0 -27
- package/target/esm/clients/cli/operator/room-members.js +0 -15
- package/target/esm/clients/cli/operator/watch.js +0 -15
- package/target/esm/clients/cli/shared/budget.js +0 -50
- package/target/esm/core/agents/turns/budgets.js +0 -48
- package/target/esm/core/agents/turns/dispatch.js +0 -40
- package/target/esm/core/agents/turns/exhaustion.js +0 -16
- package/target/esm/core/agents/turns/grants.js +0 -36
- package/target/esm/core/agents/turns/index.js +0 -33
- package/target/esm/core/agents/turns/limits.js +0 -11
- package/target/esm/core/agents/turns/messages.js +0 -32
- package/target/esm/core/agents/turns/pings.js +0 -30
- package/target/esm/core/agents/turns/plan.js +0 -29
- package/target/esm/core/agents/turns/poll.js +0 -48
- package/target/esm/core/agents/turns/results.js +0 -40
- package/target/esm/core/agents/turns/seat.js +0 -44
- package/target/esm/core/agents/turns/types.js +0 -3
- package/target/esm/core/hub/decisions/budget.js +0 -40
- package/target/esm/core/hub/decisions/check.js +0 -43
- package/target/esm/core/hub/decisions/index.js +0 -10
- package/target/esm/core/hub/decisions/proposal.js +0 -73
- package/target/esm/core/hub/decisions/usage.js +0 -39
- package/target/esm/core/hub/usecases/budget.js +0 -78
- package/target/esm/core/hub/usecases/propose.js +0 -39
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// messages: what wakes the watcher: new messages for its agent and retries of failed turns, grouped by grant and work.
|
|
2
|
-
import { isOpen, taskFor, taskOf } from '../../hub/work/index.js';
|
|
3
|
-
import { HIDDEN } from '../../hub/rooms/index.js';
|
|
4
|
-
import { note, short } from '../../../lib/index.js';
|
|
5
|
-
import { unread, wakes } from '../../hub/protocol/index.js';
|
|
6
|
-
export const collectMessages = (c, R, logs, st, all) => {
|
|
7
|
-
const { msgs: fresh, cursors } = unread(logs, c.me, st.watched ?? st.seen ?? {}, Infinity, HIDDEN);
|
|
8
|
-
const retry = (st.pending ?? []).flatMap(id => {
|
|
9
|
-
const m = all.get(id);
|
|
10
|
-
if (!m)
|
|
11
|
-
return [];
|
|
12
|
-
const task = taskFor(all, m, R.profile);
|
|
13
|
-
return !task || isOpen(taskOf(all, task, R.profile), R.profile) ? [m] : [];
|
|
14
|
-
});
|
|
15
|
-
const msgs = [...new Map([...retry, ...fresh].map(m => [m.id, m])).values()];
|
|
16
|
-
const go = msgs.filter(m => wakes(m, c.me, HIDDEN) && m.hopEff < c.maxHop && (!c.allow || c.allow.has(m.from)));
|
|
17
|
-
for (const m of msgs)
|
|
18
|
-
if (!go.includes(m))
|
|
19
|
-
note(`watch: not waking for ${short(m.id)} from ${m.from} (to ${m.to.join(',')}, hop ${m.hopEff})`);
|
|
20
|
-
return { retry, cursors, go };
|
|
21
|
-
};
|
|
22
|
-
export const groupMessages = (R, all, go) => {
|
|
23
|
-
const groups = new Map();
|
|
24
|
-
for (const m of go) {
|
|
25
|
-
const task = taskFor(all, m, R.profile), g = task ? taskOf(all, task, R.profile).grant : null;
|
|
26
|
-
if (!groups.has(g))
|
|
27
|
-
groups.set(g, { grant: g, msgs: [], task });
|
|
28
|
-
groups.get(g).msgs.push(m);
|
|
29
|
-
}
|
|
30
|
-
return groups;
|
|
31
|
-
};
|
|
32
|
-
//# sourceMappingURL=messages.js.map
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// pings: the watcher answers pings for its agent without waking a model.
|
|
2
|
-
import { append, pongText } from '../../hub/rooms/index.js';
|
|
3
|
-
import { note, short } from '../../../lib/index.js';
|
|
4
|
-
const allowed = (c, m, answered) => {
|
|
5
|
-
const ping = !m.bad && m.kind === 'ping' && m.to.includes(c.me);
|
|
6
|
-
const wake = m.hopEff < c.maxHop && (!c.allow || c.allow.has(m.from));
|
|
7
|
-
return ping && !answered.has(m.id) && wake;
|
|
8
|
-
};
|
|
9
|
-
const answerAuthor = (c, R, msgs, last, answered) => {
|
|
10
|
-
const at = msgs.findIndex(m => m.id === last);
|
|
11
|
-
for (const m of msgs.slice(at + 1)) {
|
|
12
|
-
if (!allowed(c, m, answered))
|
|
13
|
-
continue;
|
|
14
|
-
append(R, c.me, pongText(c.me, m, 'has a watcher'));
|
|
15
|
-
note(`watch: answered heartbeat ${short(m.id)} from ${m.from}`);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
export const answerWatchPings = (c, R, logs, st) => {
|
|
19
|
-
const heartbeatSeen = { ...st.heartbeatSeen };
|
|
20
|
-
const answered = new Set((logs.get(c.me) ?? []).filter(m => m.kind === 'pong').map(m => m.reId));
|
|
21
|
-
for (const [id, msgs] of logs) {
|
|
22
|
-
if (id === c.me)
|
|
23
|
-
continue;
|
|
24
|
-
answerAuthor(c, R, msgs, heartbeatSeen[id], answered);
|
|
25
|
-
if (msgs.length)
|
|
26
|
-
heartbeatSeen[id] = msgs.at(-1).id;
|
|
27
|
-
}
|
|
28
|
-
return heartbeatSeen;
|
|
29
|
-
};
|
|
30
|
-
//# sourceMappingURL=pings.js.map
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// plan: one loop's turns: what arrived, grouped and reserved; planning and dispatch take locks in the order grant,
|
|
2
|
-
// reader, sessions.
|
|
3
|
-
import { locked } from '../../hub/ports/index.js';
|
|
4
|
-
import { link } from '../../hub/protocol/index.js';
|
|
5
|
-
import { loadRoomUnlocked, loadState, readerLock, saveState } from '../../hub/history/index.js';
|
|
6
|
-
import { reserveGroups } from './budgets.js';
|
|
7
|
-
import { collectMessages, groupMessages } from './messages.js';
|
|
8
|
-
import { answerWatchPings } from './pings.js';
|
|
9
|
-
const planUnlocked = (c, R) => {
|
|
10
|
-
const { logs, st } = loadRoomUnlocked(R, c.me);
|
|
11
|
-
if (st.inflight) {
|
|
12
|
-
st.seen = st.inflight.seen;
|
|
13
|
-
st.inflight = undefined;
|
|
14
|
-
saveState(R, c.me, st);
|
|
15
|
-
}
|
|
16
|
-
const all = link(logs), heartbeatSeen = answerWatchPings(c, R, logs, st);
|
|
17
|
-
const { retry, cursors, go } = collectMessages(c, R, logs, st, all);
|
|
18
|
-
const p = reserveGroups(c, R, all, groupMessages(R, all, go), st);
|
|
19
|
-
if (p.lost)
|
|
20
|
-
return { runs: [], hold: true, lost: true };
|
|
21
|
-
const pending = [...new Set([...retry.map(m => m.id), ...p.runs.flatMap(r => r.msgs.map(m => m.id))])];
|
|
22
|
-
saveState(R, c.me, {
|
|
23
|
-
...loadState(R, c.me), heartbeatSeen, exhausted: p.exhausted, pending, runs: p.ownRuns,
|
|
24
|
-
...(p.hold ? {} : { watched: cursors })
|
|
25
|
-
});
|
|
26
|
-
return { runs: p.runs, hold: p.hold, lost: false };
|
|
27
|
-
};
|
|
28
|
-
export const planTurns = (c, R) => locked(`grant-${R.hub.name}`, () => readerLock(R, c.me, () => planUnlocked(c, R)));
|
|
29
|
-
//# sourceMappingURL=plan.js.map
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// poll: each loop renews the watcher's seat and finds its room still there, the same, with its agent a member.
|
|
2
|
-
import { locked } from '../../hub/ports/index.js';
|
|
3
|
-
import { openHub, openRoom, start } from '../../hub/rooms/index.js';
|
|
4
|
-
import { stamp } from '../../../lib/index.js';
|
|
5
|
-
import { dropSession, readSession, saveSession } from '../sessions/index.js';
|
|
6
|
-
const refreshSeat = (c) => locked('sessions', () => {
|
|
7
|
-
const cur = readSession(c.handle);
|
|
8
|
-
if (cur?.state === 'active')
|
|
9
|
-
saveSession({ ...cur, lastSeen: stamp(), pid: process.pid });
|
|
10
|
-
return cur;
|
|
11
|
-
});
|
|
12
|
-
const currentRoom = (c) => {
|
|
13
|
-
const H = openHub(c.H0.name, { refresh: true }), R = openRoom(H, c.R0.name, { quiet: true });
|
|
14
|
-
if (!R || R.genesis !== c.R0.genesis) {
|
|
15
|
-
locked('sessions', () => { if (readSession(c.handle)?.genesis === c.R0.genesis)
|
|
16
|
-
dropSession(c.handle); });
|
|
17
|
-
c.log(`watch: ${c.R0.name} is gone, recreated or archived; ${c.me} left it`);
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
if (!R.members[c.me]) {
|
|
21
|
-
c.log(`watch: ${c.me} was removed from ${c.R0.name}; stopping`);
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return R;
|
|
25
|
-
};
|
|
26
|
-
const quorum = (c, R) => {
|
|
27
|
-
const st = start(R);
|
|
28
|
-
if (st.started)
|
|
29
|
-
return { done: false, R };
|
|
30
|
-
if (c.w.once)
|
|
31
|
-
c.log(`watch: ${c.R0.name} waits for a quorum of ${st.quorum} agents and has ${st.agents}; nothing to do yet`);
|
|
32
|
-
return { done: !!c.w.once };
|
|
33
|
-
};
|
|
34
|
-
export const pollWatcher = (c) => {
|
|
35
|
-
const s = refreshSeat(c);
|
|
36
|
-
if (!s) {
|
|
37
|
-
c.log(`watch: the seat of ${c.me} was released or taken over; stopping`);
|
|
38
|
-
return { done: true };
|
|
39
|
-
}
|
|
40
|
-
if (s.state === 'stopped') {
|
|
41
|
-
if (c.w.once)
|
|
42
|
-
c.log('watch: stopped by the operator');
|
|
43
|
-
return { done: !!c.w.once };
|
|
44
|
-
}
|
|
45
|
-
const R = currentRoom(c);
|
|
46
|
-
return R ? quorum(c, R) : { done: true };
|
|
47
|
-
};
|
|
48
|
-
//# sourceMappingURL=poll.js.map
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// results: a turn's end: its usage receipt settled, and its messages delivered or kept for a retry.
|
|
2
|
-
import { OPERATOR, append } from '../../hub/rooms/index.js';
|
|
3
|
-
import { note } from '../../../lib/index.js';
|
|
4
|
-
import { compose } from '../../hub/protocol/index.js';
|
|
5
|
-
import { loadState, readerLock, saveState } from '../../hub/history/index.js';
|
|
6
|
-
import { dispatch } from './dispatch.js';
|
|
7
|
-
const settle = (c, R, run, result) => append(R, c.me, compose(`usage: settle a run of ${c.me}${result.why ? ` (${result.why})` : ''}`, [['To', OPERATOR], ['Kind', 'usage'], ['Grant', run.grant], ['Usage', 'settle'], ['Run', run.run], ['Seconds', String(result.seconds)], ['Exit', String(result.exit)]]));
|
|
8
|
-
const delivered = (c, R, run, r) => readerLock(R, c.me, () => {
|
|
9
|
-
const st = loadState(R, c.me), ids = new Set(run.msgs.map(m => m.id));
|
|
10
|
-
const success = r.code === 0 || r.timedOut;
|
|
11
|
-
saveState(R, c.me, {
|
|
12
|
-
...st, inflight: undefined, ...(success
|
|
13
|
-
? { pending: (st.pending ?? []).filter(id => !ids.has(id)) }
|
|
14
|
-
: { seen: st.inflight?.seen ?? st.seen })
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
const ended = (c, R, run, r) => {
|
|
18
|
-
note(`watch: exit ${r.code ?? r.signal}${r.timedOut ? ' (stopped at the grant\'s run-seconds)' : ''} after ${r.seconds}s`);
|
|
19
|
-
if (run.run)
|
|
20
|
-
settle(c, R, run, { seconds: r.seconds, exit: r.code ?? 128 });
|
|
21
|
-
delivered(c, R, run, r);
|
|
22
|
-
};
|
|
23
|
-
export const runTurns = async (c, R, runs) => {
|
|
24
|
-
let exitCode = 0;
|
|
25
|
-
for (const run of runs) {
|
|
26
|
-
const next = dispatch(c, R, run);
|
|
27
|
-
if (next.why) {
|
|
28
|
-
note(`watch: not starting the next turn: ${next.why}`);
|
|
29
|
-
if (run.run)
|
|
30
|
-
settle(c, R, run, { seconds: 0, exit: 125, why: `not started: ${next.why}` });
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
const r = await next.done;
|
|
34
|
-
ended(c, R, run, r);
|
|
35
|
-
if (r.code !== 0 || r.timedOut)
|
|
36
|
-
exitCode ||= r.code || 128;
|
|
37
|
-
}
|
|
38
|
-
return exitCode;
|
|
39
|
-
};
|
|
40
|
-
//# sourceMappingURL=results.js.map
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// seat: the watcher takes its agent's seat, from a dead watcher or by an explicit handoff from the agent's own chat.
|
|
2
|
-
import { locked } from '../../hub/ports/index.js';
|
|
3
|
-
import { config, openHub, openRoom } from '../../hub/rooms/index.js';
|
|
4
|
-
import { fail, stamp } from '../../../lib/index.js';
|
|
5
|
-
import { dropSession, liveness, parseSelector, pidAlive, saveSession, seatHolder, whoAmI } from '../sessions/index.js';
|
|
6
|
-
import { validateWatch } from './limits.js';
|
|
7
|
-
const handoff = (c, holder) => {
|
|
8
|
-
const own = c.w.handoff && holder?.product !== 'watch' && holder?.handle === whoAmI().handle;
|
|
9
|
-
if (c.w.handoff && !own)
|
|
10
|
-
fail('watch --handoff needs the invoking chat to own this seat');
|
|
11
|
-
if (own && holder.state === 'stopped')
|
|
12
|
-
fail('this chat is stopped; continue it before a handoff');
|
|
13
|
-
const live = holder && (holder.product !== 'watch' || (holder.pid && pidAlive(holder.pid)));
|
|
14
|
-
if (holder && !own && live)
|
|
15
|
-
fail(`${c.R0.name}/${c.me} is held by session ${holder.handle} (${liveness(holder)}); one seat per agent. Its chat may use watch --handoff`);
|
|
16
|
-
return !!own;
|
|
17
|
-
};
|
|
18
|
-
const claim = (c) => locked('sessions', () => {
|
|
19
|
-
const { w, H0, R0, me, handle } = c;
|
|
20
|
-
const holder = seatHolder(H0.name, R0.genesis, me, handle), handed = handoff(c, holder);
|
|
21
|
-
if (holder)
|
|
22
|
-
dropSession(holder.handle);
|
|
23
|
-
saveSession({
|
|
24
|
-
handle, product: 'watch', hub: H0.name, room: R0.name, genesis: R0.genesis, agent: me, scope: w.scope ?? holder?.scope ?? null,
|
|
25
|
-
state: holder?.state === 'stopped' ? 'stopped' : 'active', since: stamp(), lastSeen: stamp(), pid: process.pid,
|
|
26
|
-
takenFrom: handed ? holder.handle : holder?.takenFrom
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
export const createWatcher = (w, log) => {
|
|
30
|
-
validateWatch(w);
|
|
31
|
-
const sel = parseSelector(w.seat), H0 = openHub(sel.hub ?? w.hub ?? config().hub), R0 = openRoom(H0, sel.room), me = sel.agent;
|
|
32
|
-
if (R0.archived)
|
|
33
|
-
fail(`${R0.name} is archived`);
|
|
34
|
-
if (!R0.members[me])
|
|
35
|
-
fail(`${me} is not a member of ${R0.name}; the operator adds it with a join, or watch another agent`);
|
|
36
|
-
const raw = `watch-${H0.name}-${R0.genesis.slice(0, 8)}-${me}-${process.pid}`, handle = `env:${raw}`;
|
|
37
|
-
const c = {
|
|
38
|
-
w, H0, R0, me, raw, handle, cmd: w.exec, log, interval: (w.interval ?? 5) * 1000,
|
|
39
|
-
allow: w.from ? new Set(w.from) : null, maxHop: w.maxHop ?? 8, maxRuns: w.maxRuns ?? 10, span: w.window ?? 86400, runSeq: 0
|
|
40
|
-
};
|
|
41
|
-
claim(c);
|
|
42
|
-
return c;
|
|
43
|
-
};
|
|
44
|
-
//# sourceMappingURL=seat.js.map
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// budget: what a decision grants (a budget), until when a proposal waits (its due time), and who may decide it.
|
|
2
|
-
import { fail } from '../../../lib/index.js';
|
|
3
|
-
export const ROOT = 'operator';
|
|
4
|
-
const BUDGET_KEYS = ['runs', 'run-seconds', 'concurrency'];
|
|
5
|
-
export const ids = (v) => v === null ? [] : [...new Set(v.split(',').map(x => x.trim()).filter(Boolean))];
|
|
6
|
-
const PART = /^([a-z][a-z-]*)=(\d{1,9})$/;
|
|
7
|
-
// `runs=5, run-seconds=600, concurrency=1`; undefined if malformed. `runs` is required: a budget without a run count
|
|
8
|
-
// bounds nothing; a zero run time or concurrency would disable the bound it names.
|
|
9
|
-
export const parseBudget = (s) => {
|
|
10
|
-
if (s === null || s === undefined)
|
|
11
|
-
return null;
|
|
12
|
-
const b = {};
|
|
13
|
-
for (const part of String(s).split(',').map(x => x.trim()).filter(Boolean)) {
|
|
14
|
-
const m = PART.exec(part);
|
|
15
|
-
if (!m || !BUDGET_KEYS.includes(m[1]) || m[1] in b)
|
|
16
|
-
return undefined;
|
|
17
|
-
b[m[1]] = Number(m[2]);
|
|
18
|
-
}
|
|
19
|
-
return bounded(b) ? { 'run-seconds': 1800, concurrency: 1, ...b } : undefined;
|
|
20
|
-
};
|
|
21
|
-
const bounded = (b) => 'runs' in b && b['run-seconds'] !== 0 && b.concurrency !== 0;
|
|
22
|
-
export const budgetText = (b) => BUDGET_KEYS.map(k => `${k}=${b[k]}`).join(', ');
|
|
23
|
-
const UNIT = { m: 60e3, h: 3600e3, d: 86400e3 };
|
|
24
|
-
// Relative (`90m`, `2h`, `3d`) or an ISO 8601 time → ISO UTC to the second.
|
|
25
|
-
export const parseDue = (s) => {
|
|
26
|
-
const rel = /^(\d{1,6})([mhd])$/.exec(s);
|
|
27
|
-
const t = rel ? Date.now() + Number(rel[1]) * UNIT[rel[2]] : Date.parse(s);
|
|
28
|
-
if (!Number.isFinite(t))
|
|
29
|
-
fail(`--due takes 90m, 2h, 3d or an ISO time, not ${s}`);
|
|
30
|
-
return new Date(t).toISOString().slice(0, 19) + 'Z';
|
|
31
|
-
};
|
|
32
|
-
const within = (cap, budget) => BUDGET_KEYS.every(k => Number.isFinite(cap[k]) && budget[k] <= cap[k]);
|
|
33
|
-
// The root always may; anyone else only within the authority table, and never on their own proposal.
|
|
34
|
-
export const mayDecide = (authority, decider, proposer, budget) => {
|
|
35
|
-
if (decider === ROOT)
|
|
36
|
-
return true;
|
|
37
|
-
const cap = decider === proposer ? undefined : authority?.decide?.[decider];
|
|
38
|
-
return !!cap && (!budget || within(cap, budget));
|
|
39
|
-
};
|
|
40
|
-
//# sourceMappingURL=budget.js.map
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// check: the extension's trailers are part of a record's validity; a malformed decision record routes nowhere.
|
|
2
|
-
import { ID_RE, one } from '../protocol/index.js';
|
|
3
|
-
import { ids, parseBudget } from './budget.js';
|
|
4
|
-
export const STAMPS = ['approve', 'object', 'abstain'];
|
|
5
|
-
export const DECISIONS = ['approved', 'rejected', 'revoked'];
|
|
6
|
-
const SINGLE = ['decider', 'reviewers', 'due', 'budget', 'supersedes', 'stamp', 'decision', 'control', 'grant', 'usage', 'run', 'seconds', 'exit'];
|
|
7
|
-
const RUN = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
8
|
-
// A trailer absent, or a message id.
|
|
9
|
-
const refOk = (m, k, isRef) => one(m, k) === null || isRef(one(m, k));
|
|
10
|
-
const answers = (m, values, k) => !!m.re && values.includes(one(m, k) ?? '');
|
|
11
|
-
const receipt = (m, isRef) => isRef(one(m, 'grant') ?? '') && ['reserve', 'settle'].includes(one(m, 'usage') ?? '') && RUN.test(one(m, 'run') ?? '');
|
|
12
|
-
const dueOk = (v) => v === null || Number.isFinite(Date.parse(v));
|
|
13
|
-
// What each kind of record must carry, as (wrong, why) in the order the reasons are reported.
|
|
14
|
-
const BY_KIND = {
|
|
15
|
-
proposal: (m, isRef) => [
|
|
16
|
-
[() => !ID_RE.test(one(m, 'decider') ?? ''), 'proposal without a valid Decider'],
|
|
17
|
-
[() => !ids(one(m, 'reviewers')).every(x => ID_RE.test(x)), 'malformed Reviewers'],
|
|
18
|
-
[() => !dueOk(one(m, 'due')), 'malformed Due'],
|
|
19
|
-
[() => !refOk(m, 'supersedes', isRef), 'malformed Supersedes'],
|
|
20
|
-
],
|
|
21
|
-
stamp: m => [[() => !answers(m, STAMPS, 'stamp'), 'stamp without Re or a valid Stamp']],
|
|
22
|
-
decision: m => [[() => !answers(m, DECISIONS, 'decision'), 'decision without Re or a valid Decision']],
|
|
23
|
-
usage: (m, isRef) => [
|
|
24
|
-
[() => !receipt(m, isRef), 'malformed usage receipt'],
|
|
25
|
-
[() => one(m, 'usage') === 'settle' && !/^\d{1,9}$/.test(one(m, 'seconds') ?? ''), 'usage settle without Seconds'],
|
|
26
|
-
],
|
|
27
|
-
};
|
|
28
|
-
// Why a record is malformed for this extension, or null.
|
|
29
|
-
export const check = (m, isRef) => {
|
|
30
|
-
if (m.bad)
|
|
31
|
-
return null;
|
|
32
|
-
const twice = SINGLE.find(k => (m.trailers[k]?.length ?? 0) > 1);
|
|
33
|
-
if (twice)
|
|
34
|
-
return `duplicate ${twice}`;
|
|
35
|
-
const rules = [
|
|
36
|
-
[() => !refOk(m, 'grant', isRef), 'malformed Grant'],
|
|
37
|
-
[() => !refOk(m, 'control', isRef), 'malformed Control'],
|
|
38
|
-
[() => one(m, 'budget') !== null && !parseBudget(one(m, 'budget')), 'malformed Budget'],
|
|
39
|
-
...BY_KIND[m.kind ?? '']?.(m, isRef) ?? [],
|
|
40
|
-
];
|
|
41
|
-
return rules.find(([wrong]) => wrong())?.[1] ?? null;
|
|
42
|
-
};
|
|
43
|
-
//# sourceMappingURL=check.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// decide: the extension for decisions and budgets (capability `decisions/1`, docs/spec/decisions-1.md). A proposal circulates
|
|
2
|
-
// to named reviewers, who stamp it; only the named decider, holding authority from the hub's contract, decides. An
|
|
3
|
-
// approval that carries a budget is a grant; work spends against it, and the runner that starts model runs writes
|
|
4
|
-
// the usage receipts. Everything here is a pure fold over messages; nothing trusts a model's own report.
|
|
5
|
-
export const CAPABILITY = 'decisions/1';
|
|
6
|
-
export * from './budget.js';
|
|
7
|
-
export * from './check.js';
|
|
8
|
-
export * from './proposal.js';
|
|
9
|
-
export * from './usage.js';
|
|
10
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
// proposal: one proposal folded over the messages that answer it, in thread order: its stamps, its decision, a
|
|
2
|
-
// revocation, a superseding revision; and the grant an approval with a budget makes.
|
|
3
|
-
import { one, ordered } from '../protocol/index.js';
|
|
4
|
-
import { ROOT, ids, mayDecide, parseBudget } from './budget.js';
|
|
5
|
-
const byOwner = (m, p) => m.from === p.from || m.from === ROOT;
|
|
6
|
-
// A revision supersedes only when its author is the proposer or the root.
|
|
7
|
-
const revises = (m, p) => m.kind === 'proposal' && one(m, 'supersedes') === p.id && byOwner(m, p);
|
|
8
|
-
const answers = (m, p) => m.reId === p.id && (m.kind === 'stamp' || m.kind === 'decision');
|
|
9
|
-
const among = (all, keep) => ordered([...all.values()].filter(m => !m.bad && keep(m)));
|
|
10
|
-
// A decision names the room's control commit it was made under (`Control`); its authority is the table in effect
|
|
11
|
-
// there. A commit that does not exist yet cannot be named, so a later contract never promotes or demotes a decision.
|
|
12
|
-
const authorized = (c, m) => {
|
|
13
|
-
if (m.from === ROOT)
|
|
14
|
-
return true;
|
|
15
|
-
const table = c.authorityAt(one(m, 'control'));
|
|
16
|
-
return !!table && mayDecide(table, c.decider, c.p.from, c.budget);
|
|
17
|
-
};
|
|
18
|
-
const decides = (c, m) => one(m, 'decision') !== 'revoked' && m.from === c.decider && authorized(c, m);
|
|
19
|
-
const revokes = (c, m) => one(m, 'decision') === 'revoked' && (m.from === c.decider || m.from === ROOT);
|
|
20
|
-
const approved = (f) => f.decision?.decision === 'approved';
|
|
21
|
-
// One answer applied: a reviewer's latest stamp counts (its position relative to the decision is only a same-second
|
|
22
|
-
// tie); the first decision of the decider with authority; then the first revocation of an approval.
|
|
23
|
-
const take = (c, f, m) => {
|
|
24
|
-
if (m.kind === 'stamp' && c.reviewers.includes(m.from))
|
|
25
|
-
f.stamps[m.from] = { stamp: one(m, 'stamp'), id: m.id, reason: m.subject };
|
|
26
|
-
if (m.kind !== 'decision')
|
|
27
|
-
return;
|
|
28
|
-
if (!f.decision && decides(c, m))
|
|
29
|
-
f.decision = { decision: one(m, 'decision'), id: m.id, by: m.from, time: m.time };
|
|
30
|
-
else if (approved(f) && !f.revoked && revokes(c, m))
|
|
31
|
-
f.revoked = { id: m.id, by: m.from, time: m.time };
|
|
32
|
-
};
|
|
33
|
-
// The answers in thread order; one after the superseding revision no longer applies to this revision, except a
|
|
34
|
-
// revocation of an earlier approval.
|
|
35
|
-
const fold = (c, list, superseded) => {
|
|
36
|
-
const f = { stamps: {}, decision: null, revoked: null };
|
|
37
|
-
for (const m of ordered([...list, ...(superseded ? [superseded] : [])])) {
|
|
38
|
-
if (m === superseded)
|
|
39
|
-
break;
|
|
40
|
-
take(c, f, m);
|
|
41
|
-
}
|
|
42
|
-
const late = superseded && approved(f) && !f.revoked ? list.find(m => revokes(c, m)) : undefined;
|
|
43
|
-
if (late)
|
|
44
|
-
f.revoked = { id: late.id, by: late.from, time: late.time };
|
|
45
|
-
return f;
|
|
46
|
-
};
|
|
47
|
-
const stateOf = (f, superseded, due, now) => {
|
|
48
|
-
if (f.revoked)
|
|
49
|
-
return 'revoked';
|
|
50
|
-
if (f.decision)
|
|
51
|
-
return f.decision.decision;
|
|
52
|
-
if (superseded)
|
|
53
|
-
return 'superseded';
|
|
54
|
-
return due && now > due ? 'overdue' : 'open';
|
|
55
|
-
};
|
|
56
|
-
export const proposalOf = (all, p, authorityAt, now = Date.now() / 1000) => {
|
|
57
|
-
const c = { p, decider: one(p, 'decider'), reviewers: ids(one(p, 'reviewers')), budget: parseBudget(one(p, 'budget')) ?? null, authorityAt };
|
|
58
|
-
const due = one(p, 'due') ? Date.parse(one(p, 'due')) / 1000 : null;
|
|
59
|
-
const superseded = among(all, m => revises(m, p))[0] ?? null;
|
|
60
|
-
const f = fold(c, among(all, m => answers(m, p)), superseded);
|
|
61
|
-
const grant = approved(f) && c.budget ? { id: f.decision.id, budget: c.budget, proposal: p, revoked: !!f.revoked } : null;
|
|
62
|
-
return { proposal: p, decider: c.decider, reviewers: c.reviewers, budget: c.budget, due, ...f,
|
|
63
|
-
missing: c.reviewers.filter(r => !f.stamps[r]), superseded, state: stateOf(f, superseded, due, now), grant };
|
|
64
|
-
};
|
|
65
|
-
export const proposals = (all, authorityAt) => [...all.values()].filter(m => !m.bad && m.kind === 'proposal').map(p => proposalOf(all, p, authorityAt));
|
|
66
|
-
const decision = (d) => !!d && !d.bad && d.kind === 'decision';
|
|
67
|
-
export const grantOf = (all, authorityAt, id) => {
|
|
68
|
-
const d = all.get(id);
|
|
69
|
-
const p = decision(d) && d.reId ? all.get(d.reId) : undefined;
|
|
70
|
-
const g = p ? proposalOf(all, p, authorityAt).grant : null;
|
|
71
|
-
return g?.id === id ? g : null;
|
|
72
|
-
};
|
|
73
|
-
//# sourceMappingURL=proposal.js.map
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// usage: what a grant's runs spent, from the usage receipts the runners write.
|
|
2
|
-
import { one, ordered } from '../protocol/index.js';
|
|
3
|
-
import { grantOf } from './proposal.js';
|
|
4
|
-
// The receipts a grant's ledger counts, in thread order: every reservation, and for each run (its runner and Run id)
|
|
5
|
-
// the first settlement after its reservation, as the first decision counts. A settlement of no reserved run counts
|
|
6
|
-
// nothing.
|
|
7
|
-
const ledger = (all, grant) => {
|
|
8
|
-
const reserves = [], settled = new Map(), reserved = new Set();
|
|
9
|
-
for (const m of ordered([...all.values()].filter(m => !m.bad && m.kind === 'usage' && one(m, 'grant') === grant.id))) {
|
|
10
|
-
const run = `${m.from}/${one(m, 'run')}`;
|
|
11
|
-
if (one(m, 'usage') === 'reserve') {
|
|
12
|
-
reserves.push(m);
|
|
13
|
-
reserved.add(run);
|
|
14
|
-
}
|
|
15
|
-
else if (reserved.has(run) && !settled.has(run))
|
|
16
|
-
settled.set(run, m);
|
|
17
|
-
}
|
|
18
|
-
return { reserves, settled };
|
|
19
|
-
};
|
|
20
|
-
// Whether a receipt counts: its Grant names a grant, and that grant's ledger holds it.
|
|
21
|
-
export const counted = (all, authorityAt, m) => {
|
|
22
|
-
const g = !m.bad && m.kind === 'usage' ? grantOf(all, authorityAt, one(m, 'grant') ?? '') : null;
|
|
23
|
-
if (!g)
|
|
24
|
-
return false;
|
|
25
|
-
const { reserves, settled } = ledger(all, g);
|
|
26
|
-
return reserves.includes(m) || [...settled.values()].includes(m);
|
|
27
|
-
};
|
|
28
|
-
// Usage from the runners' receipts: every reservation is a run, settled or not; an unsettled one is open while its
|
|
29
|
-
// run could still be going, and uncertain after that.
|
|
30
|
-
export const usageOf = (all, grant, now = Date.now() / 1000) => {
|
|
31
|
-
const { reserves, settled } = ledger(all, grant);
|
|
32
|
-
let seconds = 0;
|
|
33
|
-
for (const m of settled.values())
|
|
34
|
-
seconds += Number(one(m, 'seconds'));
|
|
35
|
-
const unsettled = reserves.filter(m => !settled.has(`${m.from}/${one(m, 'run')}`));
|
|
36
|
-
const open = unsettled.filter(m => now - m.time <= grant.budget['run-seconds'] + 60);
|
|
37
|
-
return { runs: reserves.length, open: open.length, uncertain: unsettled.length - open.length, seconds, left: Math.max(0, grant.budget.runs - reserves.length) };
|
|
38
|
-
};
|
|
39
|
-
//# sourceMappingURL=usage.js.map
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// budget: proposals, reviewers' stamps, the decider's decision, and the grants it makes (decisions/1). Each operation
|
|
2
|
-
// takes who acts and typed input, and returns data; interfaces print it.
|
|
3
|
-
import * as D from '../decisions/index.js';
|
|
4
|
-
import { locked } from '../ports/index.js';
|
|
5
|
-
import { OPERATOR, append } from '../rooms/index.js';
|
|
6
|
-
import { fail, note, short } from '../../../lib/index.js';
|
|
7
|
-
import { checkOutgoing, compose, lookup } from '../protocol/index.js';
|
|
8
|
-
import { linked } from '../history/index.js';
|
|
9
|
-
import { isRef } from './actor.js';
|
|
10
|
-
import { grantView, proposalView } from './view.js';
|
|
11
|
-
const proposalAt = (all, ref) => {
|
|
12
|
-
const p = lookup(all, ref);
|
|
13
|
-
if (p.kind !== 'proposal' || p.bad)
|
|
14
|
-
fail(`${short(p.id)} is not a proposal`);
|
|
15
|
-
return p;
|
|
16
|
-
};
|
|
17
|
-
export const stamp = ({ R, me }, ref, s, text) => {
|
|
18
|
-
const { all } = linked(R, me);
|
|
19
|
-
const p = proposalAt(all, ref);
|
|
20
|
-
if (!D.STAMPS.includes(s))
|
|
21
|
-
fail(`usage: stamp <proposal> ${D.STAMPS.join('|')} -m <reason>`);
|
|
22
|
-
const info = D.proposalOf(all, p, R.authorityAt);
|
|
23
|
-
if (!info.reviewers.includes(me))
|
|
24
|
-
fail(`you are not a named reviewer of ${short(p.id)} (reviewers: ${info.reviewers.join(', ') || 'none'})`);
|
|
25
|
-
if (info.decision)
|
|
26
|
-
fail(`${short(p.id)} is already ${info.decision.decision}`);
|
|
27
|
-
const out = compose(text, [['To', [...new Set([p.from, info.decider])].filter(x => x !== me).join(', ') || p.from], ['Re', p.id], ['Kind', 'stamp'], ['Stamp', s], ['Hop', String(p.hopEff + 1)]]);
|
|
28
|
-
checkOutgoing(out, isRef(R));
|
|
29
|
-
return { proposal: p, id: append(R, me, out) };
|
|
30
|
-
};
|
|
31
|
-
// Why the actor may not revoke the proposal's approval: only its decider or the operator revokes, and only an approval.
|
|
32
|
-
const revokeProblem = (info, p, me) => {
|
|
33
|
-
if (info.state !== 'approved')
|
|
34
|
-
return `${short(p.id)} is ${info.state}; only an approval can be revoked`;
|
|
35
|
-
return me !== info.decider && me !== OPERATOR ? `only ${info.decider} or the operator revokes it` : null;
|
|
36
|
-
};
|
|
37
|
-
// Why the actor may not decide the proposal: only its named decider with authority, once, on its latest revision.
|
|
38
|
-
const decideProblem = ({ R, me }, info, p) => {
|
|
39
|
-
if (me !== info.decider)
|
|
40
|
-
return `only the named decider, ${info.decider}, decides ${short(p.id)}`;
|
|
41
|
-
if (!D.mayDecide(R.contract.authority, me, p.from, info.budget))
|
|
42
|
-
return `${me} has no authority to decide this${info.budget ? ` budget (${D.budgetText(info.budget)})` : ''} in hub ${R.hub.name}, or it is your own proposal`;
|
|
43
|
-
if (info.decision)
|
|
44
|
-
return `${short(p.id)} is already ${info.decision.decision}`;
|
|
45
|
-
return info.superseded ? `${short(p.id)} was superseded by ${short(info.superseded.id)}; decide that revision` : null;
|
|
46
|
-
};
|
|
47
|
-
// The decision names the control entry, and so the authority, it was made under (decisions/1).
|
|
48
|
-
export const decide = ({ R, me }, ref, d, text) => locked(`grant-${R.hub.name}`, () => {
|
|
49
|
-
const { all } = linked(R, me);
|
|
50
|
-
const p = proposalAt(all, ref);
|
|
51
|
-
if (!D.DECISIONS.includes(d))
|
|
52
|
-
fail(`usage: decide <proposal> ${D.DECISIONS.join('|')} -m <reason>`);
|
|
53
|
-
const info = D.proposalOf(all, p, R.authorityAt);
|
|
54
|
-
const why = d === 'revoked' ? revokeProblem(info, p, me) : decideProblem({ R, me }, info, p);
|
|
55
|
-
if (why)
|
|
56
|
-
fail(why);
|
|
57
|
-
const to = [...new Set([p.from, ...info.reviewers])].filter(x => x !== me);
|
|
58
|
-
const out = compose(text, [['To', to.join(', ') || '*'], ['Re', p.id], ['Kind', 'decision'], ['Decision', d], ['Control', R.head], ['Hop', String(p.hopEff + 1)]]);
|
|
59
|
-
checkOutgoing(out, isRef(R));
|
|
60
|
-
const id = append(R, me, out);
|
|
61
|
-
const approved = d === 'approved';
|
|
62
|
-
if (approved && info.missing.length)
|
|
63
|
-
note(`decided without stamps from ${info.missing.join(', ')}`);
|
|
64
|
-
return { proposal: p, id, budget: approved ? info.budget : null };
|
|
65
|
-
});
|
|
66
|
-
// Open proposals (with `all`, every one).
|
|
67
|
-
export const proposals = ({ R, me }, { all: everything = false } = {}) => {
|
|
68
|
-
const { all } = linked(R, me);
|
|
69
|
-
const ps = D.proposals(all, R.authorityAt).filter(x => everything || ['open', 'overdue'].includes(x.state));
|
|
70
|
-
return { proposals: ps, views: ps.map(proposalView) };
|
|
71
|
-
};
|
|
72
|
-
export const grants = ({ R, me }) => {
|
|
73
|
-
const { all } = linked(R, me);
|
|
74
|
-
return D.proposals(all, R.authorityAt).filter(x => x.grant).map(x => grantView(x, D.usageOf(all, x.grant)));
|
|
75
|
-
};
|
|
76
|
-
export { propose } from './propose.js';
|
|
77
|
-
export { DECISIONS, STAMPS, budgetText } from '../decisions/index.js';
|
|
78
|
-
//# sourceMappingURL=budget.js.map
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// propose: a proposal for the decider and its reviewers, its budget and revision checked before it is written
|
|
2
|
-
// (decisions/1).
|
|
3
|
-
import * as D from '../decisions/index.js';
|
|
4
|
-
import { OPERATOR, append } from '../rooms/index.js';
|
|
5
|
-
import { fail, note, short } from '../../../lib/index.js';
|
|
6
|
-
import { ID_RE, checkOutgoing, compose, lookup } from '../protocol/index.js';
|
|
7
|
-
import { linked } from '../history/index.js';
|
|
8
|
-
import { isRef } from './actor.js';
|
|
9
|
-
// Named reviewers, the proposer never among them.
|
|
10
|
-
const reviewersOf = (me, x) => {
|
|
11
|
-
if (!ID_RE.test(x.decider))
|
|
12
|
-
fail(`bad decider ${x.decider}`);
|
|
13
|
-
const reviewers = [...new Set(x.reviewers ?? [])].filter(r => r !== me);
|
|
14
|
-
for (const r of reviewers)
|
|
15
|
-
if (!ID_RE.test(r))
|
|
16
|
-
fail(`bad reviewer ${r}`);
|
|
17
|
-
return reviewers;
|
|
18
|
-
};
|
|
19
|
-
const budgetOf = (x) => x.budget ? D.parseBudget(x.budget) ?? fail('--budget takes runs=<n>[, run-seconds=<n>][, concurrency=<n>]') : null;
|
|
20
|
-
// A revision supersedes the actor's own proposal; the operator may revise anyone's.
|
|
21
|
-
const supersededOf = (all, me, ref) => {
|
|
22
|
-
const sup = ref ? lookup(all, ref) : null;
|
|
23
|
-
const mine = sup?.kind === 'proposal' && (sup.from === me || me === OPERATOR);
|
|
24
|
-
return !sup || mine ? sup : fail(`${short(sup.id)} is not your proposal`);
|
|
25
|
-
};
|
|
26
|
-
export const propose = ({ R, me }, x) => {
|
|
27
|
-
const { all } = linked(R, me);
|
|
28
|
-
const reviewers = reviewersOf(me, x), budget = budgetOf(x), sup = supersededOf(all, me, x.supersedes);
|
|
29
|
-
if (budget && !D.mayDecide(R.contract.authority, x.decider, me, budget))
|
|
30
|
-
note(`${x.decider} has no authority for this budget in hub ${R.hub.name}; only operator could approve it`);
|
|
31
|
-
const to = [...new Set([...reviewers, x.decider])].filter(t => t !== me);
|
|
32
|
-
const text = compose(x.text, [['To', to.join(', ') || '*'], ['Kind', 'proposal'], ['Decider', x.decider], ['Reviewers', reviewers.join(', ')],
|
|
33
|
-
['Due', x.due && D.parseDue(x.due)], ['Budget', budget && D.budgetText(budget)], ['Supersedes', sup?.id]]);
|
|
34
|
-
const bad = D.check(checkOutgoing(text, isRef(R)), isRef(R));
|
|
35
|
-
if (bad)
|
|
36
|
-
fail(`refusing to send: ${bad}`);
|
|
37
|
-
return { id: append(R, me, text, x.files ?? []), to, budget };
|
|
38
|
-
};
|
|
39
|
-
//# sourceMappingURL=propose.js.map
|