@idleware/ringi 0.6.1 → 0.7.1

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.
Files changed (162) hide show
  1. package/README.md +7 -18
  2. package/package.json +1 -1
  3. package/src/main/resources/profiles/a2a-1.json +0 -4
  4. package/src/main/resources/skill/ringi/SKILL.md +2 -18
  5. package/target/dts/adapters/host-posix/index.d.ts +2 -2
  6. package/target/dts/adapters/transport-git/branches.d.ts +2 -3
  7. package/target/dts/adapters/transport-git/index.d.ts +1 -2
  8. package/target/dts/adapters/transport-git/remote/admit.d.ts +2 -2
  9. package/target/dts/adapters/transport-git/remote/heartbeat.d.ts +2 -2
  10. package/target/dts/adapters/transport-git/remote/keys.d.ts +3 -3
  11. package/target/dts/adapters/transport-git/remote/types.d.ts +0 -1
  12. package/target/dts/adapters/transport-git/remote.d.ts +1 -1
  13. package/target/dts/adapters/transport-git/repo.d.ts +0 -1
  14. package/target/dts/adapters/transport-git/signing.d.ts +3 -3
  15. package/target/dts/clients/cli/agent/index.d.ts +0 -2
  16. package/target/dts/clients/cli/shared/index.d.ts +0 -1
  17. package/target/dts/core/agents/heartbeat/index.d.ts +2 -5
  18. package/target/dts/core/agents/heartbeat/pulse.d.ts +1 -3
  19. package/target/dts/core/agents/overview.d.ts +1 -5
  20. package/target/dts/core/agents/ports/host.d.ts +5 -37
  21. package/target/dts/core/agents/sessions/agent.d.ts +0 -17
  22. package/target/dts/core/agents/sessions/index.d.ts +0 -1
  23. package/target/dts/core/hub/history/index.d.ts +1 -11
  24. package/target/dts/core/hub/ports/store.d.ts +1 -5
  25. package/target/dts/core/hub/protocol/model.d.ts +0 -1
  26. package/target/dts/core/hub/protocol/reading.d.ts +0 -1
  27. package/target/dts/core/hub/rooms/index.d.ts +0 -4
  28. package/target/dts/core/hub/rooms/messages.d.ts +0 -1
  29. package/target/dts/core/hub/rooms/rooms.d.ts +1 -1
  30. package/target/dts/core/hub/signals/model.d.ts +0 -2
  31. package/target/dts/core/hub/usecases/hubs.d.ts +0 -1
  32. package/target/dts/core/hub/usecases/room.d.ts +0 -1
  33. package/target/dts/core/hub/usecases/rooms.d.ts +1 -5
  34. package/target/dts/core/hub/usecases/send.d.ts +0 -1
  35. package/target/dts/core/hub/usecases/view.d.ts +0 -22
  36. package/target/dts/core/hub/work/index.d.ts +0 -1
  37. package/target/dts/index.d.ts +0 -1
  38. package/target/esm/adapters/host-posix/index.js +30 -32
  39. package/target/esm/adapters/transport-git/branches.js +13 -20
  40. package/target/esm/adapters/transport-git/index.js +8 -9
  41. package/target/esm/adapters/transport-git/marks.js +4 -10
  42. package/target/esm/adapters/transport-git/remote/admit.js +8 -10
  43. package/target/esm/adapters/transport-git/remote/heartbeat-push.js +1 -1
  44. package/target/esm/adapters/transport-git/remote/heartbeat.js +4 -4
  45. package/target/esm/adapters/transport-git/remote/keys.js +7 -32
  46. package/target/esm/adapters/transport-git/remote/main.js +1 -4
  47. package/target/esm/adapters/transport-git/remote.js +1 -1
  48. package/target/esm/adapters/transport-git/repo.js +5 -3
  49. package/target/esm/adapters/transport-git/signing.js +12 -17
  50. package/target/esm/adapters/transport-git/transport.js +6 -6
  51. package/target/esm/clients/cli/agent/index.js +4 -10
  52. package/target/esm/clients/cli/arg-parse.js +1 -1
  53. package/target/esm/clients/cli/commands.js +6 -7
  54. package/target/esm/clients/cli/help.js +6 -13
  55. package/target/esm/clients/cli/operator/heartbeat-format.js +1 -1
  56. package/target/esm/clients/cli/operator/hub-commands.js +1 -1
  57. package/target/esm/clients/cli/operator/index.js +1 -1
  58. package/target/esm/clients/cli/operator/room.js +46 -7
  59. package/target/esm/clients/cli/operator/where-format.js +1 -3
  60. package/target/esm/clients/cli/shared/index.js +1 -2
  61. package/target/esm/clients/cli/shared/messages.js +1 -1
  62. package/target/esm/clients/cli/shared/profile.js +2 -2
  63. package/target/esm/clients/cli/shared/work.js +1 -1
  64. package/target/esm/composition/index.js +1 -3
  65. package/target/esm/core/agents/heartbeat/index.js +16 -23
  66. package/target/esm/core/agents/heartbeat/pulse.js +18 -33
  67. package/target/esm/core/agents/heartbeat/tools.js +1 -2
  68. package/target/esm/core/agents/overview.js +5 -6
  69. package/target/esm/core/agents/ports/host.js +2 -2
  70. package/target/esm/core/agents/sessions/agent.js +3 -23
  71. package/target/esm/core/agents/sessions/binding.js +5 -7
  72. package/target/esm/core/agents/sessions/index.js +3 -7
  73. package/target/esm/core/agents/sessions/join.js +1 -5
  74. package/target/esm/core/agents/sessions/resume.js +3 -4
  75. package/target/esm/core/agents/sessions/wait.js +10 -31
  76. package/target/esm/core/hub/capabilities.js +4 -2
  77. package/target/esm/core/hub/history/index.js +3 -1
  78. package/target/esm/core/hub/ports/store.js +3 -3
  79. package/target/esm/core/hub/protocol/index.js +3 -3
  80. package/target/esm/core/hub/protocol/model.js +2 -3
  81. package/target/esm/core/hub/protocol/reading.js +1 -3
  82. package/target/esm/core/hub/rooms/compat.js +1 -2
  83. package/target/esm/core/hub/rooms/hubs.js +2 -5
  84. package/target/esm/core/hub/rooms/index.js +3 -3
  85. package/target/esm/core/hub/rooms/messages.js +5 -10
  86. package/target/esm/core/hub/rooms/rooms.js +9 -38
  87. package/target/esm/core/hub/signals/effective.js +1 -21
  88. package/target/esm/core/hub/signals/index.js +2 -2
  89. package/target/esm/core/hub/signals/pins.js +1 -1
  90. package/target/esm/core/hub/usecases/hubs.js +0 -3
  91. package/target/esm/core/hub/usecases/room.js +2 -3
  92. package/target/esm/core/hub/usecases/rooms.js +4 -17
  93. package/target/esm/core/hub/usecases/roster.js +1 -1
  94. package/target/esm/core/hub/usecases/send.js +2 -12
  95. package/target/esm/core/hub/usecases/view.js +2 -6
  96. package/target/esm/core/hub/work/task.js +4 -11
  97. package/target/esm/index.js +1 -2
  98. package/target/esm/lib/json.js +1 -1
  99. package/target/dts/adapters/host-posix/group-state.d.ts +0 -7
  100. package/target/dts/adapters/host-posix/groups.d.ts +0 -1
  101. package/target/dts/adapters/host-posix/launch.d.ts +0 -7
  102. package/target/dts/adapters/host-posix/probes.d.ts +0 -5
  103. package/target/dts/adapters/host-posix/stats.d.ts +0 -9
  104. package/target/dts/adapters/transport-kafka/index.d.ts +0 -2
  105. package/target/dts/adapters/transport-temporal/index.d.ts +0 -2
  106. package/target/dts/clients/cli/operator/room-lifecycle.d.ts +0 -5
  107. package/target/dts/clients/cli/operator/room-list.d.ts +0 -5
  108. package/target/dts/clients/cli/operator/room-members.d.ts +0 -4
  109. package/target/dts/clients/cli/operator/watch.d.ts +0 -2
  110. package/target/dts/clients/cli/shared/budget.d.ts +0 -7
  111. package/target/dts/core/agents/turns/budgets.d.ts +0 -5
  112. package/target/dts/core/agents/turns/dispatch.d.ts +0 -9
  113. package/target/dts/core/agents/turns/exhaustion.d.ts +0 -11
  114. package/target/dts/core/agents/turns/grants.d.ts +0 -4
  115. package/target/dts/core/agents/turns/index.d.ts +0 -4
  116. package/target/dts/core/agents/turns/limits.d.ts +0 -2
  117. package/target/dts/core/agents/turns/messages.d.ts +0 -10
  118. package/target/dts/core/agents/turns/pings.d.ts +0 -5
  119. package/target/dts/core/agents/turns/plan.d.ts +0 -3
  120. package/target/dts/core/agents/turns/poll.d.ts +0 -8
  121. package/target/dts/core/agents/turns/results.d.ts +0 -3
  122. package/target/dts/core/agents/turns/seat.d.ts +0 -2
  123. package/target/dts/core/agents/turns/types.d.ts +0 -59
  124. package/target/dts/core/hub/decisions/budget.d.ts +0 -15
  125. package/target/dts/core/hub/decisions/check.d.ts +0 -6
  126. package/target/dts/core/hub/decisions/index.d.ts +0 -5
  127. package/target/dts/core/hub/decisions/proposal.d.ts +0 -38
  128. package/target/dts/core/hub/decisions/usage.d.ts +0 -12
  129. package/target/dts/core/hub/usecases/budget.d.ts +0 -23
  130. package/target/dts/core/hub/usecases/propose.d.ts +0 -17
  131. package/target/esm/adapters/host-posix/group-state.js +0 -24
  132. package/target/esm/adapters/host-posix/groups.js +0 -43
  133. package/target/esm/adapters/host-posix/launch.js +0 -80
  134. package/target/esm/adapters/host-posix/probes.js +0 -66
  135. package/target/esm/adapters/host-posix/stats.js +0 -27
  136. package/target/esm/adapters/transport-kafka/index.js +0 -4
  137. package/target/esm/adapters/transport-temporal/index.js +0 -4
  138. package/target/esm/clients/cli/operator/room-lifecycle.js +0 -23
  139. package/target/esm/clients/cli/operator/room-list.js +0 -27
  140. package/target/esm/clients/cli/operator/room-members.js +0 -15
  141. package/target/esm/clients/cli/operator/watch.js +0 -15
  142. package/target/esm/clients/cli/shared/budget.js +0 -50
  143. package/target/esm/core/agents/turns/budgets.js +0 -48
  144. package/target/esm/core/agents/turns/dispatch.js +0 -40
  145. package/target/esm/core/agents/turns/exhaustion.js +0 -16
  146. package/target/esm/core/agents/turns/grants.js +0 -36
  147. package/target/esm/core/agents/turns/index.js +0 -33
  148. package/target/esm/core/agents/turns/limits.js +0 -11
  149. package/target/esm/core/agents/turns/messages.js +0 -32
  150. package/target/esm/core/agents/turns/pings.js +0 -30
  151. package/target/esm/core/agents/turns/plan.js +0 -29
  152. package/target/esm/core/agents/turns/poll.js +0 -48
  153. package/target/esm/core/agents/turns/results.js +0 -40
  154. package/target/esm/core/agents/turns/seat.js +0 -44
  155. package/target/esm/core/agents/turns/types.js +0 -3
  156. package/target/esm/core/hub/decisions/budget.js +0 -40
  157. package/target/esm/core/hub/decisions/check.js +0 -43
  158. package/target/esm/core/hub/decisions/index.js +0 -10
  159. package/target/esm/core/hub/decisions/proposal.js +0 -73
  160. package/target/esm/core/hub/decisions/usage.js +0 -39
  161. package/target/esm/core/hub/usecases/budget.js +0 -78
  162. package/target/esm/core/hub/usecases/propose.js +0 -39
@@ -1,7 +1,6 @@
1
1
  import './composition/index.ts';
2
2
  export * from './core/hub/protocol/index.ts';
3
3
  export * from './core/hub/work/index.ts';
4
- export * as decide from './core/hub/decisions/index.ts';
5
4
  export * as signals from './core/hub/signals/index.ts';
6
5
  export type { Store, Transport, Descriptor, StoredRoom, StoredMessage, SignalRecord, Rev } from './core/hub/ports/store.ts';
7
6
  export { openHub, openRoom, createHub, contractAt } from './core/hub/rooms/index.ts';
@@ -1,35 +1,33 @@
1
- // host-posix: macOS/Linux process facts and launch control behind ProcessHost (core/agents/ports/host.ts). Its handles belong
2
- // only to this instance. A bounded observation never grants authority to stop an otherwise running process.
3
- import { fail } from '../../lib/fail.js';
4
- import { treeCpu } from './stats.js';
5
- import { launch } from './launch.js';
6
- import { alive, ancestors, snapshot, supported } from './probes.js';
7
- const systemClock = { wall: () => Date.now(), monotonic: () => performance.now() };
8
- const observe = (done, deadline, clock) => {
9
- if (deadline !== undefined && !Number.isFinite(deadline))
10
- fail('a process observation deadline must be finite');
11
- const held = setInterval(() => { }, 1000);
12
- if (deadline === undefined)
13
- return done.finally(() => clearInterval(held));
14
- return new Promise((resolve, reject) => {
15
- const timer = setTimeout(() => { clearInterval(held); resolve(null); }, Math.max(0, deadline - clock.monotonic()));
16
- const clear = () => { clearTimeout(timer); clearInterval(held); };
17
- done.then(value => { clear(); resolve(value); }, error => { clear(); reject(error); });
18
- });
1
+ // host-posix: what macOS and Linux say about a process, and a detached start, behind ProcessHost
2
+ // (core/agents/ports/host.ts). Elsewhere every probe says nothing rather than guess.
3
+ import { spawn, spawnSync } from 'node:child_process';
4
+ import path from 'node:path';
5
+ const supported = process.platform === 'darwin' || process.platform === 'linux';
6
+ // A process of another user that we may not signal still exists.
7
+ const alive = (pid) => {
8
+ try {
9
+ process.kill(pid, 0);
10
+ return true;
11
+ }
12
+ catch (e) {
13
+ return e.code === 'EPERM';
14
+ }
19
15
  };
20
- export const hostPosix = (clock = systemClock) => {
21
- const processes = new WeakMap();
22
- const own = (handle) => processes.get(handle) ?? fail('this process handle belongs to another host');
23
- return {
24
- supported, clock, alive, ancestors, snapshot,
25
- inspect: (pid, rows = snapshot()) => ({ alive: supported ? alive(pid) : null, cpu: treeCpu(pid, rows) }),
26
- start: request => {
27
- const handle = {};
28
- processes.set(handle, launch(request, clock));
29
- return handle;
30
- },
31
- stop: handle => own(handle).stop(),
32
- awaitQuiescence: (handle, deadline) => observe(own(handle).done, deadline, clock),
33
- };
16
+ // Up to twelve parents by name, from one `ps` call each; a sandbox that denies `ps` gets none.
17
+ const ancestors = (first) => {
18
+ const result = [];
19
+ for (let pid = first, i = 0; i < 12 && pid > 1; i++) {
20
+ const r = spawnSync('ps', ['-o', 'ppid=,comm=', '-p', String(pid)], { encoding: 'utf8' });
21
+ const m = r.status === 0 ? /^\s*(\d+)\s+(.*)$/.exec(String(r.stdout ?? '').trim()) : null;
22
+ if (!m)
23
+ break;
24
+ result.push({ pid, name: path.basename(m[2].trim()).toLowerCase() });
25
+ pid = Number(m[1]);
26
+ }
27
+ return result;
34
28
  };
29
+ export const hostPosix = () => ({
30
+ supported, alive, ancestors,
31
+ start: ({ file, args, env }) => { spawn(file, [...args], { detached: true, stdio: 'ignore', env }).unref(); },
32
+ });
35
33
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  // branches: a room is one linear branch from its genesis. What its commits are (messages or control records), the
2
- // room's control files, and the room's life: create, archive, delete.
2
+ // room's control files, and the room's life: create, delete.
3
3
  import { fail } from '../../lib/index.js';
4
4
  import * as Remote from './remote.js';
5
5
  import { ZERO } from './run.js';
@@ -9,17 +9,17 @@ import { ID_RE, parse } from '../../core/hub/protocol/index.js';
9
9
  const ROOT = 'operator';
10
10
  export const CONTROL_FILES = new Set(['ROOM.md', 'members.json', 'profile.json', 'allowed_signers']);
11
11
  export class Branches extends Repo {
12
- ref(name) { return name.includes('@') ? `refs/archive/${name}` : `refs/heads/${name}`; }
12
+ ref(name) { return `refs/heads/${name}`; }
13
13
  tip(name) { return this.rev(this.ref(name)); }
14
14
  // Every branch but the contract's, by name.
15
15
  roomBranches() {
16
16
  return this.git(['for-each-ref', '--format=%(refname:lstrip=2)', 'refs/heads/']).split('\n').filter(b => b && b !== 'main');
17
17
  }
18
- // Every branch but the contract's is a room; an archive is `<room>@<genesis8>`.
19
- roomNames(archived) {
20
- const prefix = archived ? 'refs/archive/' : 'refs/heads/';
18
+ // Every branch but the contract's is a room.
19
+ roomNames() {
20
+ const prefix = 'refs/heads/';
21
21
  const names = this.git(['for-each-ref', '--format=%(refname)', prefix]).split('\n').filter(Boolean).map(r => r.slice(prefix.length))
22
- .filter(n => ID_RE.test(n.split('@')[0]) && n !== 'main' && n.includes('@') === archived);
22
+ .filter(n => ID_RE.test(n) && n !== 'main');
23
23
  return names.sort();
24
24
  }
25
25
  // A control record: the room's genesis, or a root commit of Kind: room.
@@ -42,7 +42,7 @@ export class Branches extends Repo {
42
42
  // On a signed hub the branch's verification says which commits are control records, and why the others that are no
43
43
  // valid message are skipped; a commit off the branch's first parents is never a message (remote.ts).
44
44
  judged(room, tip, entries) {
45
- if (this.trust !== 'signed' || room.archived)
45
+ if (this.trust !== 'signed')
46
46
  return entries;
47
47
  const b = Remote.branch(this.h, { name: room.name, ref: this.ref(room.name), genesis: room.genesis }, tip);
48
48
  const control = new Set(b.control);
@@ -63,11 +63,10 @@ export class Branches extends Repo {
63
63
  return null;
64
64
  const genesis = this.git(['rev-list', '--first-parent', '--reverse', tip, '^refs/heads/main']).split('\n')[0]
65
65
  || this.git(['rev-list', '--first-parent', '--reverse', tip]).split('\n').filter(Boolean).at(-1);
66
- const archived = name.includes('@');
67
- if (this.trust === 'signed' && !archived && this.h.remoteAlarms?.[ref])
66
+ if (this.trust === 'signed' && this.h.remoteAlarms?.[ref])
68
67
  fail(`ALARM ${this.h.remoteAlarms[ref]}; room ${name} is quarantined`);
69
- const control = this.entries({ name, genesis, archived }, tip).filter(e => e.control).map(e => ({ rev: e.sha, text: e.text }));
70
- return { name, archived, head: control.at(-1).rev, genesis, tip, base: this.rev(`${genesis}^`) ?? ZERO, control };
68
+ const control = this.entries({ name, genesis }, tip).filter(e => e.control).map(e => ({ rev: e.sha, text: e.text }));
69
+ return { name, head: control.at(-1).rev, genesis, tip, base: this.rev(`${genesis}^`) ?? ZERO, control };
71
70
  }
72
71
  // Control files are read at a control record, never at the branch's tip.
73
72
  controlFile(room, name, rev) {
@@ -93,13 +92,7 @@ export class Branches extends Repo {
93
92
  refsUnder(prefix) {
94
93
  return this.git(['for-each-ref', '--format=%(refname) %(objectname)', prefix]).split('\n').filter(Boolean).map(l => l.split(' '));
95
94
  }
96
- // The branch moves under the archive; its signals stay where they are, on its commits.
97
- archiveRoom(room, as) {
98
- const tip = this.rev(this.ref(room.name)) ?? fail(`room ${room.name} is gone`);
99
- this.git(['update-ref', `refs/archive/${as}`, tip, ZERO]);
100
- this.git(['update-ref', '-d', this.ref(room.name), tip]);
101
- }
102
- // Every ref of the room in one transaction: its branch (or archive), every tag on its commits (signals, heartbeats,
95
+ // Every ref of the room in one transaction: its branch, every tag on its commits (signals, heartbeats,
103
96
  // anyone's), what the store keeps under its genesis (pins, evidence) and its unpublished commits. Objects are left to
104
97
  // the repository.
105
98
  deleteRoom(room) {
@@ -114,10 +107,10 @@ export class Branches extends Repo {
114
107
  this.git(['update-ref', '--stdin'], { input: ['start', ...refs.map(r => `delete ${r}`), 'prepare', 'commit', ''].join('\n') });
115
108
  }
116
109
  remote(room) {
117
- return { name: room.name, ref: this.ref(room.name), head: room.head, genesis: room.genesis, archived: room.archived };
110
+ return { name: room.name, ref: this.ref(room.name), head: room.head, genesis: room.genesis };
118
111
  }
119
112
  roster(room) {
120
- if (this.trust !== 'signed' || room.archived)
113
+ if (this.trust !== 'signed')
121
114
  return null;
122
115
  const v = Remote.verifyRoom(this.h, this.remote(room));
123
116
  return { members: v.members, signers: v.entries };
@@ -4,7 +4,6 @@
4
4
  // (the root's, on top of main); then every message is a commit by its author, and
5
5
  // every control record (Kind: room) a commit by the root
6
6
  // refs/tags/<agent>.<signal>.<message12> signals: annotated tags without text on the message's commit
7
- // refs/archive/<room>@<genesis8> an archived room, read-only
8
7
  // refs/ringi/accepted/<genesis>/<reader>/<author> readers' pins of each author's accepted last message
9
8
  // A member's log is its own commits in the room's branch, in branch order. The control files at the root of a room's
10
9
  // tree (members, profile, signers) change only in the root's control records.
@@ -23,10 +22,13 @@ import { CONTROL_FILES } from './branches.js';
23
22
  import { Marks } from './marks.js';
24
23
  export { runGit } from './run.js';
25
24
  import { TEXT_MAX, checkOutgoing } from '../../core/hub/protocol/index.js';
25
+ import { signingContext } from './remote/keys.js';
26
26
  export class GitStore extends Marks {
27
27
  type = 'git';
28
28
  static create(hubDir, d, connection, contract, text) {
29
29
  const { home, signing } = typeof connection === 'string' ? { home: connection, signing: undefined } : connection;
30
+ if (d.trust === 'signed')
31
+ signingContext({ name: d.name, signing });
30
32
  const dir = path.join(hubDir, d.store.path, '.git');
31
33
  // A signed hub's repository already exists: rootFor made it with the root key.
32
34
  if (!fs.existsSync(dir)) {
@@ -44,10 +46,11 @@ export class GitStore extends Marks {
44
46
  }
45
47
  // A new signed hub: the root key is generated on this machine and pinned in the descriptor.
46
48
  static rootFor(hubDir, d, home, signing) {
49
+ signingContext({ name: d.name, signing });
47
50
  const dir = path.join(hubDir, d.store.path, '.git');
48
51
  mkdirIn(home, path.dirname(dir));
49
52
  init(dir, home);
50
- return Remote.setupSigned({ name: d.name, dir, d, signing }, home, d.store.remote).root;
53
+ return Remote.setupSigned({ name: d.name, dir, d, signing }, d.store.remote).root;
51
54
  }
52
55
  logIds(room) { return [...new Set(this.live(room).map(e => e.author))]; }
53
56
  logHead(room, id) { return this.live(room).filter(e => e.author === id).at(-1)?.sha ?? null; }
@@ -85,8 +88,6 @@ export class GitStore extends Marks {
85
88
  }
86
89
  // One message on top of the room's branch.
87
90
  append(room, who, text, entries) {
88
- if (room.archived)
89
- fail(`room ${room.name} is archived: read-only`);
90
91
  if (checkOutgoing(text, s => this.isRef(s)).trailers['ringi-files'])
91
92
  fail('Ringi-Files is store metadata; supply attachments instead');
92
93
  const own = entries.find(e => CONTROL_FILES.has(e.path));
@@ -103,8 +104,7 @@ export class GitStore extends Marks {
103
104
  return this.trust === 'signed' ? Remote.verify(this.h, this.remote(room), id, head) : null;
104
105
  }
105
106
  pin(room, reader, id, rev) {
106
- if (!room.archived)
107
- this.git(['update-ref', `refs/ringi/accepted/${room.genesis}/${reader}/${id}`, rev]);
107
+ this.git(['update-ref', `refs/ringi/accepted/${room.genesis}/${reader}/${id}`, rev]);
108
108
  }
109
109
  unpin(room, reader, id) {
110
110
  this.gitOk(['update-ref', '-d', `refs/ringi/accepted/${room.genesis}/${reader}/${id}`]);
@@ -118,11 +118,10 @@ export class GitStore extends Marks {
118
118
  .map(b => /^\d+ blob [0-9a-f]+ +(\d+)\t(.*)$/s.exec(b.toString('utf8'))).filter((x) => !!x)
119
119
  .filter(([, , p]) => wanted.has(p)).map(([, n, p]) => [p, Number(n)]));
120
120
  }
121
- admit(room, who, key) { return Remote.roomAdmit(this.h, this.remote(room), who, key, this.home); }
122
- joinKey(room, who) { return Remote.join(this.h, this.remote(room), who, this.home); }
121
+ admit(room, who, key) { return Remote.roomAdmit(this.h, this.remote(room), who, key); }
122
+ joinKey(room, who) { return Remote.join(this.h, this.remote(room), who); }
123
123
  // A room's transport alarm is the room's, whichever author the operator forgets.
124
124
  forgetLog(room, _id) { Remote.forget(this.h, this.remote(room)); }
125
- rootKey() { return this.d.root ?? fail(`hub ${this.d.name} has no pinned root`); }
126
125
  rootRoster() { return Remote.rootRoster(this.h); }
127
126
  }
128
127
  //# sourceMappingURL=index.js.map
@@ -2,7 +2,7 @@
2
2
  // one mutable tag `<agent>.heartbeat.<genesis12>` per agent and room, replaced on every beat.
3
3
  import * as Remote from './remote.js';
4
4
  import { signedTag } from './signing.js';
5
- import { ZERO, frames, ident, runGitRaw } from './run.js';
5
+ import { ZERO, frames, runGitRaw } from './run.js';
6
6
  import { signalTags, tagObject } from './tags.js';
7
7
  import { Branches } from './branches.js';
8
8
  import { fail, note } from '../../lib/index.js';
@@ -15,7 +15,7 @@ export class Marks extends Branches {
15
15
  heartbeat(room, who, target, text) {
16
16
  const ref = Remote.heartbeatRef(who, room.genesis), name = ref.slice('refs/tags/'.length);
17
17
  const old = this.rev(`${ref}^{tag}`) ? this.git(['rev-parse', ref]).trim() : null;
18
- const obj = this.trust === 'signed' ? Remote.makeHeartbeat(this.h, this.remote(room), who, target, text, this.home)
18
+ const obj = this.trust === 'signed' ? Remote.makeHeartbeat(this.h, this.remote(room), who, target, text)
19
19
  : this.git(['mktag'], { input: `object ${target}\ntype commit\ntag ${name}\ntagger ${who} <${who}@ringi> ${Math.floor(Date.now() / 1000)} +0000\n\n${text}` }).trim();
20
20
  if (this.gitOk(['update-ref', ref, obj, old ?? ZERO]).status !== 0)
21
21
  fail(`${name} moved meanwhile; the next beat writes it`);
@@ -45,13 +45,7 @@ export class Marks extends Branches {
45
45
  Remote.push(this.h, ref);
46
46
  return;
47
47
  }
48
- if (this.h.signing)
49
- this.git(['update-ref', ref, signedTag(this.h, who, tag, target, this.home), ZERO]);
50
- else {
51
- const sign = Remote.sign(this.h, who, this.home);
52
- const key = sign[0]?.slice(2);
53
- this.git([...(key ? ['-c', `user.signingkey=${key}`] : []), 'tag', key ? '-s' : '-a', '-F', '-', tag, target], { input: '', env: ident(who) });
54
- }
48
+ this.git(['update-ref', ref, signedTag(this.h, who, tag, target), ZERO]);
55
49
  Remote.push(this.h, ref);
56
50
  }
57
51
  // The tags named `<actor>.<signal>.<message12>`, with the objects they name; a heartbeat is no signal (heartbeats()).
@@ -75,7 +69,7 @@ export class Marks extends Branches {
75
69
  return [];
76
70
  return [{ ref: t.ref, object: t.object, name: t.name, actor: t.actor, target: o?.target ?? '', tagger: o?.tagger ?? '', text: o?.text ?? '' }];
77
71
  });
78
- if (this.trust !== 'signed' || room.archived)
72
+ if (this.trust !== 'signed')
79
73
  return records;
80
74
  return Remote.withVerification(this.h, () => records.filter(s => {
81
75
  const why = Remote.verifySignal(this.h, this.remote(room), s.ref, s.object);
@@ -3,13 +3,13 @@ import * as C from '../../../lib/index.js';
3
3
  import * as P from '../../../core/hub/protocol/index.js';
4
4
  import { branch, verifyRoom } from './branch.js';
5
5
  import { git, id, ident, isRemote, locked, rev } from './git.js';
6
- import { keyInput, identityKey, sign, signerLine } from './keys.js';
6
+ import { keyInput, identityKey, signerLine } from './keys.js';
7
7
  import { signCommit } from '../signing.js';
8
8
  import { push } from './push.js';
9
9
  import { CONTROL_FILES, ROOT } from './types.js';
10
- export const join = (H, R, who, home) => {
10
+ export const join = (H, R, who) => {
11
11
  const room = verifyRoom(H, R);
12
- const key = locked(H, () => identityKey(H, who, home, true));
12
+ const key = locked(H, () => identityKey(H, who, true));
13
13
  const admitted = !!room.entries.find(e => e.principal === who && e.key.line === key.line);
14
14
  return { publicKey: key.line, fingerprint: key.fp, admitted };
15
15
  };
@@ -30,14 +30,14 @@ const treeWith = (H, base, control, patches) => {
30
30
  return git(H, ['mktree'], { input: [...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, line]) => line).join('\n') + '\n' }).trim();
31
31
  };
32
32
  // The root's control record admitting a member, on top of the room's branch: the key and the member go in together.
33
- export const roomAdmit = (H, R, who, publicKey, home) => {
33
+ export const roomAdmit = (H, R, who, publicKey) => {
34
34
  const key = admissionKey(H, R, who, publicKey);
35
35
  for (let attempt = 0; attempt < 8; attempt++) {
36
36
  const tip = rev(H, R.ref) ?? C.fail('room branch is missing');
37
37
  const room = branch(H, R, tip), head = room.control.at(-1);
38
38
  if (room.entries.some(e => e.principal === who || e.key.fp === key.fp))
39
39
  C.fail('principal or key is already admitted; key rotation is not implemented');
40
- const { sha, members } = admissionCommit({ H, R, who, home, tip, head, room, key });
40
+ const { sha, members } = admissionCommit({ H, R, who, tip, head, room, key });
41
41
  if (git(H, ['update-ref', R.ref, sha, tip], { ok: true }).status !== 0)
42
42
  continue;
43
43
  if (!push(H, R.ref, { behind: true }))
@@ -48,14 +48,14 @@ export const roomAdmit = (H, R, who, publicKey, home) => {
48
48
  }
49
49
  return C.fail('room kept moving while admitting; retry');
50
50
  };
51
- const admissionCommit = ({ H, who, home, tip, head, room, key }) => {
51
+ const admissionCommit = ({ H, who, tip, head, room, key }) => {
52
52
  const members = { ...room.members, [who]: { role: 'member', since: new Date().toISOString() } };
53
53
  const roster = [...room.entries.map(e => signerLine(e.principal, e.key)), signerLine(who, key)].join('');
54
54
  const tree = treeWith(H, tip, head, { 'members.json': JSON.stringify(members, null, 2) + '\n', allowed_signers: roster });
55
- const sha = git(H, ['commit-tree', ...(H.signing ? [] : sign(H, ROOT, home)), tree, '-p', tip], {
55
+ const sha = git(H, ['commit-tree', tree, '-p', tip], {
56
56
  input: P.compose(`admit ${who}`, [['To', '*'], ['Kind', 'room'], ['Key', key.fp]]), env: ident(ROOT),
57
57
  }).trim();
58
- return { sha: signCommit(H, ROOT, sha, home), members };
58
+ return { sha: signCommit(H, ROOT, sha), members };
59
59
  };
60
60
  const admissionKey = (H, R, who, publicKey) => {
61
61
  id(who);
@@ -63,8 +63,6 @@ const admissionKey = (H, R, who, publicKey) => {
63
63
  C.fail('room admit is for signed hubs; local membership is added by join');
64
64
  if (who === ROOT)
65
65
  C.fail('the root is already pinned; root replacement requires a separate trust transition');
66
- if (R.archived)
67
- C.fail('cannot admit into an archived room');
68
66
  const key = keyInput(publicKey);
69
67
  return key;
70
68
  };
@@ -5,7 +5,7 @@ import { checkHeartbeat, heartbeatRef } from './heartbeat.js';
5
5
  import { checkUrl } from './keys.js';
6
6
  import { SHA, TAG_ACCEPT, TAG_TRACK, TAGS } from './types.js';
7
7
  // Publish only the actor's exact heartbeat with an explicit old OID. A racing beat is ordinary contention, never a
8
- // signal rewrite alarm. Neither success nor failure changes immutable acceptance pins or budget/history state.
8
+ // signal rewrite alarm. Neither success nor failure changes immutable acceptance pins or history state.
9
9
  export const pushHeartbeat = (H, R, who, sha, expected) => {
10
10
  const ref = heartbeatRef(who, R.genesis);
11
11
  if (!SHA.test(sha) || (expected !== null && !SHA.test(expected)))
@@ -24,9 +24,9 @@ export const verifyHeartbeat = (H, R, ref, sha) => {
24
24
  return reason(e);
25
25
  }
26
26
  };
27
- // Keep the transport's existing call contract while object construction has one named input.
28
- export const makeHeartbeat = (...[H, R, who, target, text, home]) => createHeartbeat({ H, R, who, target, text, home });
29
- const createHeartbeat = ({ H, R, who, target, text, home }) => {
27
+ // Object construction has one named input; signing needs no custody path.
28
+ export const makeHeartbeat = (...[H, R, who, target, text]) => createHeartbeat({ H, R, who, target, text });
29
+ const createHeartbeat = ({ H, R, who, target, text }) => {
30
30
  const ref = heartbeatRef(who, R.genesis);
31
31
  if (!SHA.test(target))
32
32
  C.fail('heartbeat target must be a full commit id');
@@ -35,7 +35,7 @@ const createHeartbeat = ({ H, R, who, target, text, home }) => {
35
35
  const body = `Room: ${R.genesis}\n${text.trimEnd()}\n`;
36
36
  heartbeatBody(body);
37
37
  const raw = `object ${target}\ntype commit\ntag ${ref.slice(TAGS.length)}\ntagger ${who} <${who}@ringi> ${Math.floor(Date.now() / 1000)} +0000\n\n${body}`;
38
- const signature = signBytes(H, who, raw, home);
38
+ const signature = signBytes(H, who, raw);
39
39
  const sha = git(H, ['mktag'], { input: raw + signature }).trim();
40
40
  if (isRemote(H))
41
41
  checkHeartbeat(H, R, ref, sha);
@@ -1,10 +1,8 @@
1
1
  // keys: public keys as given and pinned, a signed hub's root and its signers, and which key signs for a principal.
2
2
  import crypto from 'node:crypto';
3
3
  import fs from 'node:fs';
4
- import path from 'node:path';
5
4
  import * as C from '../../../lib/index.js';
6
- import { config, id, isRemote, mkdir, rev } from './git.js';
7
- import { command } from './process.js';
5
+ import { id, rev } from './git.js';
8
6
  import { ROOT } from './types.js';
9
7
  export const parseKey = (value) => {
10
8
  const text = String(value || '').trim();
@@ -20,47 +18,24 @@ export const parseKey = (value) => {
20
18
  export const keyInput = (value) => parseKey(fs.existsSync(value) && fs.statSync(value).isFile() ? fs.readFileSync(value, 'utf8') : value);
21
19
  export const rootKey = (H) => parseKey(H.d.root || C.fail(`hub ${H.name} has no out-of-band root pin; use hub add --root <public-key|file>`));
22
20
  export const signerLine = (who, key) => `${who} namespaces="git" ${key.line}\n`;
23
- const keyPath = (H, who, home) => path.join(home, 'keys', id(H.name), id(who));
24
- const localKey = (H, who, home, create = false) => {
25
- const file = keyPath(H, who, home);
26
- if (!fs.existsSync(file)) {
27
- if (!create)
28
- C.fail(`no private key for ${who} on this machine; ${who === ROOT ? 'root keys are never obtained from a hub' : 'join here to generate one, then ask the root to admit its public key'}`);
29
- mkdir(path.dirname(file));
30
- command('ssh-keygen', ['-q', '-t', 'ed25519', '-N', '', '-C', `${who}@ringi`, '-f', file]);
31
- }
32
- // Derive from the private key instead of trusting a mutable .pub sidecar.
33
- const key = parseKey(command('ssh-keygen', ['-y', '-f', file]));
34
- return { ...key, file };
21
+ export const signingContext = (H) => H.signing ?? C.fail(`hub ${H.name}: signed writes require an injected Signer`);
22
+ export const identityKey = (H, who, create = false) => {
23
+ const context = signingContext(H);
24
+ return parseKey(context.signer.publicIdentity(context.resolve(id(H.name), id(who), create)).key);
35
25
  };
36
- export const identityKey = (H, who, home, create = false) => H.signing
37
- ? parseKey(H.signing.signer.publicIdentity(H.signing.resolve(id(H.name), id(who), create)).key)
38
- : localKey(H, who, home, create);
39
- export const setupSigned = (H, home, url) => {
26
+ export const setupSigned = (H, url) => {
40
27
  id(H.name);
41
28
  if (rev(H, 'refs/heads/main'))
42
29
  C.fail('setupSigned only initializes an empty hub; it cannot replace an existing trust anchor');
43
- const key = identityKey(H, ROOT, home, true);
30
+ const key = identityKey(H, ROOT, true);
44
31
  H.d.trust = 'signed';
45
32
  H.d.root = key.line;
46
- config(H, 'gpg.format', 'ssh');
47
- config(H, 'gpg.ssh.program', 'ssh-keygen');
48
33
  if (url) {
49
34
  checkUrl(url);
50
35
  H.d.store.remote = url;
51
36
  }
52
37
  return { root: key.line, allowedSigners: signerLine(ROOT, key) };
53
38
  };
54
- export const sign = (H, who, home) => {
55
- if (!isRemote(H))
56
- return [];
57
- const key = localKey(H, who, home);
58
- if (who === ROOT && key.line !== rootKey(H).line)
59
- C.fail('the local operator key does not match this hub\'s pinned root');
60
- config(H, 'gpg.format', 'ssh');
61
- config(H, 'gpg.ssh.program', 'ssh-keygen');
62
- return [`-S${key.file}`];
63
- };
64
39
  export const rootRoster = (H) => signerLine(ROOT, rootKey(H));
65
40
  const supportedUrl = (url) => {
66
41
  const malformed = /[\r\n\0]/.test(url) || url.startsWith('-');
@@ -1,7 +1,7 @@
1
1
  // main: the hub's contract branch verified: every commit signed by the root since the pinned genesis.
2
2
  import * as C from '../../../lib/index.js';
3
3
  import { memo } from './cache.js';
4
- import { git, isRemote, rev } from './git.js';
4
+ import { isRemote, rev } from './git.js';
5
5
  import { parseKey, rootKey } from './keys.js';
6
6
  import { blobText } from './roster.js';
7
7
  import { chain, checkSignature, linear } from './signature.js';
@@ -34,8 +34,5 @@ const checkContract = (H, sha, key) => {
34
34
  const meta = C.strictJson(blobText(H, sha, name), name);
35
35
  if (meta.schema !== 'ringi-hub' || meta.trust !== 'signed' || parseKey(meta.root).line !== key.line)
36
36
  C.fail(`${name} must declare signed trust and the pinned root key`);
37
- const authority = git(H, ['cat-file', '-e', `${sha}:authority.json`], { ok: true });
38
- if (authority.status === 0)
39
- C.strictJson(blobText(H, sha, 'authority.json'), 'authority.json');
40
37
  };
41
38
  //# sourceMappingURL=main.js.map
@@ -1,7 +1,7 @@
1
1
  export { withVerification } from './remote/cache.js';
2
2
  export { gitEnv, gitTimeout, checkGitTimeout } from './remote/process.js';
3
3
  export { isRemote } from './remote/git.js';
4
- export { parseKey, setupSigned, sign, rootRoster } from './remote/keys.js';
4
+ export { parseKey, setupSigned, rootRoster } from './remote/keys.js';
5
5
  export { verifyHub } from './remote/main.js';
6
6
  export { readCommits } from './remote/commits.js';
7
7
  export { branch, verifyRoom, verify } from './remote/branch.js';
@@ -8,6 +8,7 @@ import { mkdirIn } from '../fs/index.js';
8
8
  import * as Remote from './remote.js';
9
9
  import { ZERO, ident, runGit, runGitOk } from './run.js';
10
10
  import { signCommit } from './signing.js';
11
+ import { signingContext } from './remote/keys.js';
11
12
  // The store is a repository whose work tree holds the contract (`main`) as it is now, so Git clients (SourceTree, IDEs)
12
13
  // open `store/` without errors, on the contract, with every room's branch beside it; `git status` stays clean.
13
14
  export const init = (dir, home) => runGit(['init', '-q', '-b', 'main', path.dirname(dir)], { where: home });
@@ -24,6 +25,8 @@ export class Repo {
24
25
  this.trust = d.trust;
25
26
  this.dir = path.join(hubDir, d.store.path, '.git');
26
27
  this.h = { name: d.name, dir: this.dir, d, ...(d.trust === 'signed' && signing ? { signing } : {}) };
28
+ if (this.trust === 'signed')
29
+ signingContext(this.h);
27
30
  if (!fs.existsSync(this.dir))
28
31
  fail(`hub ${d.name}: no git store at ${this.dir}`);
29
32
  this.openable();
@@ -82,8 +85,8 @@ export class Repo {
82
85
  }
83
86
  // Every commit is made here: on a signed hub, signed by its author's key on this machine.
84
87
  commit(who, text, tree, parents) {
85
- const sha = this.git(['commit-tree', ...(this.h.signing ? [] : Remote.sign(this.h, who, this.home)), tree, ...parents.flatMap(p => ['-p', p])], { input: text, env: ident(who) }).trim();
86
- return signCommit(this.h, who, sha, this.home);
88
+ const sha = this.git(['commit-tree', tree, ...parents.flatMap(p => ['-p', p])], { input: text, env: ident(who) }).trim();
89
+ return signCommit(this.h, who, sha);
87
90
  }
88
91
  advance(ref, sha, old) {
89
92
  if (this.gitOk(['update-ref', ref, sha, old ?? ZERO]).status !== 0)
@@ -102,7 +105,6 @@ export class Repo {
102
105
  const r = this.gitOk(['show', `${rev}:${name}`]);
103
106
  return r.status === 0 ? r.stdout : null;
104
107
  }
105
- contractFiles(rev) { return this.git(['ls-tree', '-r', '--name-only', rev]).split('\n').filter(Boolean); }
106
108
  amendContract(who, text, files) {
107
109
  const head = this.contractHead();
108
110
  const sha = this.commit(who, text, this.tree(`${head}^{tree}`, Object.entries(files).map(([p, c]) => ({ path: p, content: c, mode: '100644' }))), [head]);
@@ -1,18 +1,13 @@
1
1
  // GitSigningBridge: Signer signs the exact unsigned Git object. Tags append its SSH armor; commits fold it into
2
- // gpgsig. Custody is injected; only legacy direct entrypoints retain their previous file-key signing default.
2
+ // gpgsig. All signing and public identity come from the injected Signer; custody stays outside the transport.
3
3
  import { fail } from '../../lib/index.js';
4
4
  import { git, id, ident, isRemote } from './remote/git.js';
5
- import { identityKey, parseKey, rootKey, sign } from './remote/keys.js';
6
- import { command } from './remote/process.js';
5
+ import { identityKey, parseKey, rootKey, signingContext } from './remote/keys.js';
7
6
  import { checkSignature } from './remote/signature.js';
8
- export const signBytes = (H, who, payload, home) => {
7
+ export const signBytes = (H, who, payload) => {
9
8
  if (!isRemote(H))
10
9
  return '';
11
- if (!H.signing) {
12
- const file = sign(H, who, home)[0].slice(2);
13
- return command('ssh-keygen', ['-Y', 'sign', '-n', 'git', '-f', file], { input: payload });
14
- }
15
- const context = H.signing, key = context.resolve(id(H.name), id(who));
10
+ const context = signingContext(H), key = context.resolve(id(H.name), id(who));
16
11
  const identity = context.signer.publicIdentity(key), publicKey = parseKey(identity.key);
17
12
  if (who === 'operator' && publicKey.line !== rootKey(H).line)
18
13
  fail('the local operator key does not match this hub\'s pinned root');
@@ -21,24 +16,24 @@ export const signBytes = (H, who, payload, home) => {
21
16
  fail('the signer returned an incompatible signature identity or format');
22
17
  return Buffer.from(signature.bytes).toString('utf8');
23
18
  };
24
- // The legacy constructor path already asked Git to sign; only an injected provider uses the neutral byte bridge.
25
- export const signCommit = (H, who, sha, home) => {
26
- if (!H.signing || !isRemote(H))
19
+ export const signCommit = (H, who, sha) => {
20
+ if (!isRemote(H))
27
21
  return sha;
28
22
  const raw = git(H, ['cat-file', 'commit', sha], { raw: true }), end = raw.indexOf('\n\n');
29
23
  if (end < 0)
30
24
  fail('malformed unsigned commit');
31
- const signature = signBytes(H, who, raw, home).trimEnd().replace(/\n/g, '\n ');
25
+ const signature = signBytes(H, who, raw).trimEnd().replace(/\n/g, '\n ');
32
26
  const body = Buffer.concat([raw.subarray(0, end), Buffer.from(`\ngpgsig ${signature}\n\n`), raw.subarray(end + 2)]);
33
27
  const signed = git(H, ['hash-object', '-t', 'commit', '-w', '--stdin'], { input: body }).trim();
34
- checkSignature(H, signed, who, identityKey(H, who, home));
28
+ checkSignature(H, signed, who, identityKey(H, who));
35
29
  return signed;
36
30
  };
37
- export const signedTag = (H, who, tag, target, home) => {
31
+ export const signedTag = (H, who, tag, target) => {
38
32
  const tagger = git(H, ['var', 'GIT_COMMITTER_IDENT'], { env: ident(who) }).trim();
39
33
  const raw = `object ${target}\ntype commit\ntag ${tag}\ntagger ${tagger}\n\n`;
40
- const signed = git(H, ['mktag'], { input: raw + signBytes(H, who, raw, home) }).trim();
41
- checkSignature(H, signed, who, identityKey(H, who, home), 'tag');
34
+ const signed = git(H, ['mktag'], { input: raw + signBytes(H, who, raw) }).trim();
35
+ if (isRemote(H))
36
+ checkSignature(H, signed, who, identityKey(H, who), 'tag');
42
37
  return signed;
43
38
  };
44
39
  //# sourceMappingURL=signing.js.map
@@ -1,12 +1,12 @@
1
1
  // transport: the git store as a store type: a hub's repository is `store/.git` in the hub's directory; a signed hub's
2
- // root key is made here (remote/keys.ts) and a published hub is replicated by bootstrap (remote/bootstrap.ts).
3
- import path from 'node:path';
2
+ // root identity comes from Signer and a published hub is replicated by bootstrap (remote/bootstrap.ts).
3
+ import { fail } from '../../lib/index.js';
4
4
  import { GitStore } from './index.js';
5
5
  import * as Remote from './remote.js';
6
6
  const context = (d, opt) => d.trust === 'signed'
7
- ? typeof opt.signer === 'function' ? opt.signer() : opt.signer
7
+ ? (typeof opt.signer === 'function' ? opt.signer() : opt.signer) ?? fail('signed hubs require an injected Signer')
8
8
  : undefined;
9
- // `home`: where this machine keeps its state; the git store keeps its keys and scratch indexes there.
9
+ // `home`: where this machine keeps its state and scratch indexes; Signer owns key custody.
10
10
  export const gitTransport = (home, opt = {}) => ({
11
11
  // One branch per room with its messages and control records, signals as tags on them (hub-1, Layout).
12
12
  layout: 'git/2',
@@ -15,13 +15,13 @@ export const gitTransport = (home, opt = {}) => ({
15
15
  open: (at, d) => new GitStore(at, d, home, context(d, opt)),
16
16
  create: (at, d, genesis) => {
17
17
  const signing = context(d, opt);
18
- if (d.trust === 'signed')
18
+ if (signing)
19
19
  d.root = GitStore.rootFor(at, d, home, signing);
20
20
  const g = genesis(d);
21
21
  const store = GitStore.create(at, d, { home, signing }, g.files, g.text);
22
22
  if (d.trust === 'signed')
23
23
  d.genesis = store.contractHead();
24
- const custody = d.trust !== 'signed' ? null : signing ? signing.custody?.(d.name, 'operator') ?? null : path.join(home, 'keys', d.name, 'operator');
24
+ const custody = signing?.custody?.(d.name, 'operator') ?? null;
25
25
  return { store, custody };
26
26
  },
27
27
  attach: (name, url, pins) => { Remote.hubAdd({ home, name, url, ...pins }); },
@@ -1,9 +1,9 @@
1
- // agent: the commands only an agent runs, as the agent its session is bound to (`ringi agent inbox`, whoami, card,
2
- // ping, wait, take); joining and leaving a room are session.ts.
1
+ // agent: the commands only an agent runs, as the agent its session is bound to (`ringi agent inbox`, whoami, wait,
2
+ // take); joining and leaving a room are session.ts.
3
3
  import * as Agent from '../../../core/agents/sessions/agent.js';
4
4
  import { asJson, render } from '../../../core/hub/usecases/view.js';
5
- import { fail, short, when } from '../../../lib/index.js';
6
- import { dump, flag, list, opt, out, textOf } from '../args.js';
5
+ import { fail, short } from '../../../lib/index.js';
6
+ import { dump, flag, opt, out } from '../args.js';
7
7
  import { scopeLine, waiting } from '../shared/index.js';
8
8
  const options = (o) => ({ peek: flag(o, 'peek'), limit: opt(o, 'limit') ? Number(opt(o, 'limit')) : undefined });
9
9
  export const showInbox = ({ R, me, s }, res, o) => {
@@ -31,12 +31,6 @@ export const whoami = (a) => {
31
31
  const { session: s, profile, members } = Agent.whoami(a);
32
32
  out(`${s.agent} in ${s.hub}/${s.room} (genesis ${short(s.genesis)}, profile ${profile}), session ${s.handle}, ${s.state}\nscope: ${scopeLine(s, a.R.quorum !== null)}\nmembers: ${members.join(', ')}`);
33
33
  };
34
- export const card = (a, o) => out(`card ${short(Agent.card(a, textOf(o)))}`);
35
- export const ping = (a, o) => {
36
- const r = Agent.ping(a, { to: o._[0] ?? fail('usage: ringi agent ping <id> [-m <note>] [--next <s>] [--re <msg>]'),
37
- text: list(o, 'm').length ? list(o, 'm').join('\n\n') : undefined, next: opt(o, 'next') ? Number(opt(o, 'next')) : undefined, re: opt(o, 're') });
38
- out(`ping ${short(r.id)} to ${r.to}${r.previous ? (r.previous.answeredAt !== null ? `; ${r.to} answered the previous one at ${when(r.previous.answeredAt)}` : `; no pong to the previous one (${short(r.previous.id)})`) : ''}`);
39
- };
40
34
  export const wait = async (a, o) => {
41
35
  const r = await Agent.wait(a, { ...options(o), max: opt(o, 'max') === undefined ? undefined : Number(opt(o, 'max')) });
42
36
  if (r.inbox)