@agentorg/cli 0.0.1 → 0.0.2

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 (90) hide show
  1. package/README.md +3 -3
  2. package/dist/src/bindings/mcp/encoding.js +1 -4
  3. package/dist/src/clients/cli/commands.js +1 -1
  4. package/dist/src/clients/cli/index.js +1 -1
  5. package/dist/src/clients/mcp/agent.d.ts +3 -5
  6. package/dist/src/clients/mcp/agent.js +10 -23
  7. package/dist/src/clients/mcp/command.d.ts +1 -1
  8. package/dist/src/clients/mcp/command.js +5 -11
  9. package/dist/src/clients/mcp/projects.d.ts +1 -2
  10. package/dist/src/clients/mcp/projects.js +5 -9
  11. package/dist/src/clients/mcp/sessions.d.ts +1 -5
  12. package/dist/src/clients/mcp/sessions.js +1 -10
  13. package/dist/src/clients/mcp/setup.js +4 -8
  14. package/dist/src/clients/mcp/waiter.d.ts +0 -2
  15. package/dist/src/clients/mcp/waiter.js +9 -20
  16. package/dist/src/domain/actors.js +3 -7
  17. package/dist/src/domain/attention.d.ts +8 -7
  18. package/dist/src/domain/attention.js +31 -47
  19. package/dist/src/domain/capabilities.js +0 -1
  20. package/dist/src/domain/claims.js +2 -2
  21. package/dist/src/domain/errors.d.ts +1 -1
  22. package/dist/src/domain/errors.js +1 -1
  23. package/dist/src/domain/index.d.ts +1 -15
  24. package/dist/src/domain/model/attention.d.ts +1 -5
  25. package/dist/src/domain/model/attention.js +5 -6
  26. package/dist/src/domain/model/builtins.d.ts +12 -179
  27. package/dist/src/domain/model/builtins.js +3 -6
  28. package/dist/src/domain/model/claims.d.ts +1 -3
  29. package/dist/src/domain/model/claims.js +1 -3
  30. package/dist/src/domain/model/index.d.ts +0 -7
  31. package/dist/src/domain/model/index.js +0 -3
  32. package/dist/src/domain/model/json-schema.d.ts +1 -4
  33. package/dist/src/domain/model/records.d.ts +0 -3
  34. package/dist/src/domain/model/records.js +3 -7
  35. package/dist/src/domain/model/system.d.ts +1 -5
  36. package/dist/src/domain/model/system.js +1 -5
  37. package/dist/src/domain/model/team.d.ts +4 -119
  38. package/dist/src/domain/model/team.js +8 -19
  39. package/dist/src/domain/model/workspace.d.ts +2 -86
  40. package/dist/src/domain/model/workspace.js +0 -1
  41. package/dist/src/domain/ports.d.ts +3 -4
  42. package/dist/src/domain/registry.d.ts +2 -16
  43. package/dist/src/domain/registry.js +22 -54
  44. package/dist/src/domain/system.js +1 -5
  45. package/dist/src/domain/team.d.ts +1 -1
  46. package/dist/src/domain/team.js +13 -33
  47. package/dist/src/domain/workspace.js +2 -10
  48. package/dist/src/home.d.ts +5 -5
  49. package/dist/src/home.js +6 -6
  50. package/dist/src/server/command.js +2 -2
  51. package/dist/src/server/index.d.ts +1 -1
  52. package/dist/src/server/index.js +2 -2
  53. package/dist/src/storage/sqlite/database.d.ts +5 -0
  54. package/dist/src/storage/sqlite/database.js +32 -1
  55. package/dist/src/storage/sqlite/index.js +1 -2
  56. package/dist/src/storage/sqlite/schema.d.ts +3 -4
  57. package/dist/src/storage/sqlite/schema.js +4 -4
  58. package/node_modules/@uniac/agentic-workspace/README.md +5 -5
  59. package/node_modules/@uniac/agentic-workspace/dist/examples/authority.d.ts +0 -1
  60. package/node_modules/@uniac/agentic-workspace/dist/examples/authority.js +3 -16
  61. package/node_modules/@uniac/agentic-workspace/dist/src/actors/index.d.ts +13 -13
  62. package/node_modules/@uniac/agentic-workspace/dist/src/actors/index.js +9 -25
  63. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/common.d.ts +1 -9
  64. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/common.js +2 -4
  65. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/forms.d.ts +0 -13
  66. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/forms.js +0 -3
  67. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/messages.d.ts +0 -3
  68. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/messages.js +3 -3
  69. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/work.d.ts +2 -15
  70. package/node_modules/@uniac/agentic-workspace/dist/src/contracts/work.js +9 -14
  71. package/node_modules/@uniac/agentic-workspace/dist/src/mcp/index.js +1 -1
  72. package/node_modules/@uniac/agentic-workspace/dist/src/persistence/index.d.ts +1 -4
  73. package/node_modules/@uniac/agentic-workspace/dist/src/persistence/sqlite.js +2 -15
  74. package/node_modules/@uniac/agentic-workspace/dist/src/records.d.ts +0 -7
  75. package/node_modules/@uniac/agentic-workspace/dist/src/records.js +0 -14
  76. package/node_modules/@uniac/agentic-workspace/dist/src/work/asks.d.ts +20 -19
  77. package/node_modules/@uniac/agentic-workspace/dist/src/work/asks.js +39 -41
  78. package/node_modules/@uniac/agentic-workspace/dist/src/work/index.js +18 -24
  79. package/node_modules/@uniac/agentic-workspace/docs/contracts.md +23 -29
  80. package/node_modules/@uniac/agentic-workspace/docs/design.md +13 -13
  81. package/node_modules/@uniac/agentic-workspace/docs/hub-integration.md +25 -50
  82. package/node_modules/@uniac/agentic-workspace/package.json +1 -1
  83. package/package.json +3 -3
  84. package/skills/agentorg/SKILL.md +39 -38
  85. package/skills/agentorg/references/claude-code.md +3 -4
  86. package/vendor/uniac-agentic-workspace-0.0.2-8aebc44.tgz +0 -0
  87. package/vendor/workspace.json +6 -6
  88. package/dist/src/clients/mcp/levels.d.ts +0 -17
  89. package/dist/src/clients/mcp/levels.js +0 -7
  90. package/vendor/uniac-agentic-workspace-0.0.1-26fd0ee.tgz +0 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The agentorg home: the directory holding the core's database, the root's credential, the
2
+ * The agentorg home: the directory holding the core's database, the person's credential, the
3
3
  * credential store of the positions sessions fill, and which session fills which. It is
4
4
  * `AGENTORG_HOME`, or `~/.local/share/agentorg/`. Backing up means copying it.
5
5
  */
@@ -7,9 +7,9 @@ export declare function home(): string;
7
7
  /** The home directory, created readable by its owner only when it does not exist. */
8
8
  export declare function ensureHome(): string;
9
9
  export declare function databasePath(): string;
10
- /** The root's credential, which `agentorg-server` writes on first start, mode 600. */
10
+ /** The person's credential, which `agentorg-server` writes on first start, mode 600. */
11
11
  export declare function rootCredentialPath(): string;
12
- /** The root's Actor identity, written beside its credential by `agentorg-server`. */
12
+ /** The person's Actor identity, written beside its credential by `agentorg-server`. */
13
13
  export declare function rootActorPath(): string;
14
14
  /** A file's trimmed contents, refused when other users can read it. */
15
15
  export declare function readPrivate(path: string): string;
@@ -26,9 +26,9 @@ export declare const credentials: {
26
26
  read(actor: string): string | undefined;
27
27
  remove(actor: string): void;
28
28
  };
29
- /** An actor's credential: `AGENTORG_CREDENTIAL` when set, else the root's, from its file. */
29
+ /** An actor's credential: `AGENTORG_CREDENTIAL` when set, else the person's, from its file. */
30
30
  export declare function readCredential(): string | undefined;
31
- /** Select an explicit Actor identity and its credential from the environment or local root files. */
31
+ /** Select an explicit Actor identity and its credential from the environment or the person's files. */
32
32
  export declare function readIdentity(): {
33
33
  actorId: string;
34
34
  credential: string;
package/dist/src/home.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The agentorg home: the directory holding the core's database, the root's credential, the
2
+ * The agentorg home: the directory holding the core's database, the person's credential, the
3
3
  * credential store of the positions sessions fill, and which session fills which. It is
4
4
  * `AGENTORG_HOME`, or `~/.local/share/agentorg/`. Backing up means copying it.
5
5
  */
@@ -18,11 +18,11 @@ export function ensureHome() {
18
18
  export function databasePath() {
19
19
  return join(home(), "core.db");
20
20
  }
21
- /** The root's credential, which `agentorg-server` writes on first start, mode 600. */
21
+ /** The person's credential, which `agentorg-server` writes on first start, mode 600. */
22
22
  export function rootCredentialPath() {
23
23
  return join(home(), "root-credential");
24
24
  }
25
- /** The root's Actor identity, written beside its credential by `agentorg-server`. */
25
+ /** The person's Actor identity, written beside its credential by `agentorg-server`. */
26
26
  export function rootActorPath() {
27
27
  return join(home(), "root-actor");
28
28
  }
@@ -65,13 +65,13 @@ export const credentials = {
65
65
  rmSync(credentials.path(actor), { force: true });
66
66
  },
67
67
  };
68
- /** An actor's credential: `AGENTORG_CREDENTIAL` when set, else the root's, from its file. */
68
+ /** An actor's credential: `AGENTORG_CREDENTIAL` when set, else the person's, from its file. */
69
69
  export function readCredential() {
70
70
  if (process.env.AGENTORG_CREDENTIAL)
71
71
  return process.env.AGENTORG_CREDENTIAL;
72
72
  return existsSync(rootCredentialPath()) ? readPrivate(rootCredentialPath()) : undefined;
73
73
  }
74
- /** Select an explicit Actor identity and its credential from the environment or local root files. */
74
+ /** Select an explicit Actor identity and its credential from the environment or the person's files. */
75
75
  export function readIdentity() {
76
76
  const actorId = process.env.AGENTORG_ACTOR_ID;
77
77
  const credential = process.env.AGENTORG_CREDENTIAL;
@@ -84,7 +84,7 @@ export function readIdentity() {
84
84
  if (!existsSync(rootCredentialPath()))
85
85
  return undefined;
86
86
  if (!existsSync(rootActorPath())) {
87
- throw new Error(`no root actor id in ${rootActorPath()}; restart agentorg-server`);
87
+ throw new Error(`no Actor id for the person in ${rootActorPath()}; restart agentorg-server`);
88
88
  }
89
89
  return { actorId: readPrivate(rootActorPath()), credential: readPrivate(rootCredentialPath()) };
90
90
  }
@@ -14,7 +14,7 @@ const HELP = `usage: agentorg-server [--port <n>]
14
14
  agentorg-server service install|uninstall
15
15
  agentorg-server status
16
16
 
17
- Run the organization's core on 127.0.0.1 at --port, else AGENTORG_PORT, else ${DEFAULT_CORE_PORT}. Its records and root credential are in AGENTORG_HOME, default ~/.local/share/agentorg. The first start creates the root. A missing root credential file is replaced on startup.
17
+ Run the organization's core on 127.0.0.1 at --port, else AGENTORG_PORT, else ${DEFAULT_CORE_PORT}. Its records and the person's credential are in AGENTORG_HOME, default ~/.local/share/agentorg. The first start creates the person, the user's own Actor. A missing credential file for the person is replaced on startup.
18
18
 
19
19
  service manages only the core's user service. status reports its executable, launcher, service and availability.`;
20
20
  /** Suppress only Node 22's experimental SQLite notice while loading the store. */
@@ -57,7 +57,7 @@ export async function main(args) {
57
57
  onError: (actor, error) => process.stderr.write(`${PROGRAM}: the channel of ${actor} failed to start: ${error.message}\n`),
58
58
  });
59
59
  await rootCredential(core.organization.registry, rootCredentialPath());
60
- process.stderr.write(`${PROGRAM}: core serving on ${core.url}; the root's credential is in ${rootCredentialPath()}\n`);
60
+ process.stderr.write(`${PROGRAM}: core serving on ${core.url}; the person's credential is in ${rootCredentialPath()}\n`);
61
61
  await Promise.race([once(process, "SIGINT"), once(process, "SIGTERM")]);
62
62
  await core.close();
63
63
  store.close();
@@ -38,7 +38,7 @@ export declare function startCore(store: Store, options: CoreOptions & {
38
38
  hostname?: string;
39
39
  }): Promise<RunningCore>;
40
40
  /**
41
- * The root's credential, kept in `file`: written with the root on the first start, and minted
41
+ * The person's credential, kept in `file`: written with the person on the first start, and minted
42
42
  * again when the file is gone, since only its hash is stored.
43
43
  */
44
44
  export declare function rootCredential(registry: Registry, file: string): Promise<string>;
@@ -122,7 +122,7 @@ export async function startCore(store, options) {
122
122
  };
123
123
  }
124
124
  /**
125
- * The root's credential, kept in `file`: written with the root on the first start, and minted
125
+ * The person's credential, kept in `file`: written with the person on the first start, and minted
126
126
  * again when the file is gone, since only its hash is stored.
127
127
  */
128
128
  export async function rootCredential(registry, file) {
@@ -141,7 +141,7 @@ export async function rootCredential(registry, file) {
141
141
  }
142
142
  const credential = readPrivate(file);
143
143
  if ((await registry.byCredential(credential))?.id !== root.id) {
144
- throw new Error(`${file} does not hold the root's credential; delete it and start the core again to mint a new one`);
144
+ throw new Error(`${file} does not hold the person's credential; delete it and start the core again to mint a new one`);
145
145
  }
146
146
  writePrivate(join(dirname(file), "root-actor"), root.id);
147
147
  return credential;
@@ -15,6 +15,11 @@ export declare class Database {
15
15
  private readonly kinds;
16
16
  constructor(path: string);
17
17
  private createSchema;
18
+ /**
19
+ * Schema 10: handles become unique among open Actors, not among siblings. A later open duplicate
20
+ * takes the first free `<handle>-<n>`, within the 40-character handle, from n = 2.
21
+ */
22
+ private uniqueOpenHandles;
18
23
  close(): void;
19
24
  /** Run `work` as one transaction, rolled back if it throws. */
20
25
  transaction<T>(work: () => T): T;
@@ -52,8 +52,12 @@ export class Database {
52
52
  message_id TEXT PRIMARY KEY, source_hash TEXT NOT NULL,
53
53
  recipient_id TEXT NOT NULL REFERENCES actors(id), workspace_cursor INTEGER NOT NULL
54
54
  );
55
- PRAGMA user_version = ${SCHEMA_VERSION};
55
+ PRAGMA user_version = 9;
56
56
  `));
57
+ version = 9;
58
+ }
59
+ if (version === 9) {
60
+ this.transaction(() => this.uniqueOpenHandles());
57
61
  return;
58
62
  }
59
63
  if (version !== 0) {
@@ -61,6 +65,33 @@ export class Database {
61
65
  }
62
66
  this.transaction(() => this.db.exec(`${SCHEMA}\nPRAGMA user_version = ${SCHEMA_VERSION};`));
63
67
  }
68
+ /**
69
+ * Schema 10: handles become unique among open Actors, not among siblings. A later open duplicate
70
+ * takes the first free `<handle>-<n>`, within the 40-character handle, from n = 2.
71
+ */
72
+ uniqueOpenHandles() {
73
+ const taken = new Set(this.all("SELECT name FROM actors").map((row) => String(row.name)));
74
+ const open = new Set();
75
+ for (const row of this.all("SELECT id, name FROM actors WHERE closed_at IS NULL ORDER BY rowid")) {
76
+ const handle = String(row.name);
77
+ let name = handle;
78
+ for (let n = 2; open.has(name); n += 1) {
79
+ const candidate = `${handle.slice(0, 39 - String(n).length)}-${n}`;
80
+ if (!taken.has(candidate))
81
+ name = candidate;
82
+ }
83
+ if (name !== handle)
84
+ this.run("UPDATE actors SET name = ? WHERE id = ?", [name, String(row.id)]);
85
+ taken.add(name);
86
+ open.add(name);
87
+ }
88
+ this.db.exec(`
89
+ DROP INDEX one_root;
90
+ DROP INDEX handles_unique_among_siblings;
91
+ CREATE UNIQUE INDEX handles_unique_among_open ON actors (name) WHERE closed_at IS NULL;
92
+ PRAGMA user_version = 10;
93
+ `);
94
+ }
64
95
  close() {
65
96
  this.db.close();
66
97
  }
@@ -52,8 +52,7 @@ export class SqliteStore {
52
52
  update: (actor) => this.database.update("actors", actor),
53
53
  get: (id) => this.database.get("actors", id),
54
54
  byCredentialHash: (hash) => this.database.select("actors", "credential_hash = ? AND closed_at IS NULL", [hash])[0],
55
- root: () => this.database.select("actors", "manager IS NULL")[0],
56
- reports: (manager) => this.database.select("actors", "manager = ? AND closed_at IS NULL", [manager]),
55
+ first: () => this.database.select("actors", "1", [], "rowid", 1)[0],
57
56
  open: () => this.database.select("actors", "closed_at IS NULL"),
58
57
  };
59
58
  claims = {
@@ -1,17 +1,16 @@
1
1
  /** SQLite tables and their record schemas; times are ISO 8601 UTC and credentials are hashed. */
2
- export declare const SCHEMA_VERSION = 9;
2
+ export declare const SCHEMA_VERSION = 10;
3
3
  export declare const AUDIT_INDEXES = "\nCREATE INDEX IF NOT EXISTS audit_writes ON audit (actor) WHERE outcome = 'ok';\nCREATE INDEX IF NOT EXISTS audit_targets ON audit (actor, target) WHERE outcome = 'ok';";
4
- export declare const SCHEMA = "\nCREATE TABLE actors (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n purpose TEXT NOT NULL,\n manager TEXT REFERENCES actors (id),\n credential_hash TEXT NOT NULL UNIQUE,\n created_at TEXT NOT NULL,\n closed_at TEXT\n);\nCREATE UNIQUE INDEX one_root ON actors ((manager IS NULL)) WHERE manager IS NULL;\nCREATE UNIQUE INDEX handles_unique_among_siblings ON actors (manager, name) WHERE closed_at IS NULL;\n\n-- Retired Actor-call history, retained privately; no operation reads it.\nCREATE TABLE calls (\n id TEXT PRIMARY KEY,\n \"from\" TEXT NOT NULL REFERENCES actors (id),\n \"to\" TEXT NOT NULL REFERENCES actors (id),\n interface TEXT NOT NULL,\n input JSON NOT NULL,\n state TEXT NOT NULL,\n output JSON,\n error TEXT,\n cause TEXT REFERENCES calls (id),\n run TEXT,\n created_at TEXT NOT NULL,\n delivered_at TEXT,\n settled_at TEXT\n);\nCREATE TABLE claims (\n resource TEXT PRIMARY KEY,\n holder TEXT NOT NULL REFERENCES actors (id),\n note TEXT NOT NULL,\n until TEXT,\n claimed_at TEXT NOT NULL\n);\nCREATE INDEX claims_holder ON claims (holder);\n\nCREATE TABLE workspace_imports (\n message_id TEXT PRIMARY KEY,\n source_hash TEXT NOT NULL,\n recipient_id TEXT NOT NULL REFERENCES actors(id),\n workspace_cursor INTEGER NOT NULL\n);\n\nCREATE TABLE audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n at TEXT NOT NULL,\n actor TEXT,\n operation TEXT NOT NULL,\n target TEXT,\n outcome TEXT NOT NULL\n);\n";
4
+ export declare const SCHEMA = "\nCREATE TABLE actors (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n purpose TEXT NOT NULL,\n manager TEXT REFERENCES actors (id),\n credential_hash TEXT NOT NULL UNIQUE,\n created_at TEXT NOT NULL,\n closed_at TEXT\n);\nCREATE UNIQUE INDEX handles_unique_among_open ON actors (name) WHERE closed_at IS NULL;\n\n-- Retired Actor-call history, retained privately; no operation reads it.\nCREATE TABLE calls (\n id TEXT PRIMARY KEY,\n \"from\" TEXT NOT NULL REFERENCES actors (id),\n \"to\" TEXT NOT NULL REFERENCES actors (id),\n interface TEXT NOT NULL,\n input JSON NOT NULL,\n state TEXT NOT NULL,\n output JSON,\n error TEXT,\n cause TEXT REFERENCES calls (id),\n run TEXT,\n created_at TEXT NOT NULL,\n delivered_at TEXT,\n settled_at TEXT\n);\nCREATE TABLE claims (\n resource TEXT PRIMARY KEY,\n holder TEXT NOT NULL REFERENCES actors (id),\n note TEXT NOT NULL,\n until TEXT,\n claimed_at TEXT NOT NULL\n);\nCREATE INDEX claims_holder ON claims (holder);\n\nCREATE TABLE workspace_imports (\n message_id TEXT PRIMARY KEY,\n source_hash TEXT NOT NULL,\n recipient_id TEXT NOT NULL REFERENCES actors(id),\n workspace_cursor INTEGER NOT NULL\n);\n\nCREATE TABLE audit (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n at TEXT NOT NULL,\n actor TEXT,\n operation TEXT NOT NULL,\n target TEXT,\n outcome TEXT NOT NULL\n);\n";
5
5
  export declare const TABLES: {
6
6
  readonly actors: import("zod").ZodObject<{
7
7
  id: import("zod").ZodString;
8
8
  name: import("zod").ZodString;
9
9
  purpose: import("zod").ZodString;
10
- manager: import("zod").ZodNullable<import("zod").ZodString>;
11
10
  created_at: import("zod").ZodISODateTime;
12
11
  closed_at: import("zod").ZodNullable<import("zod").ZodISODateTime>;
13
12
  credential_hash: import("zod").ZodString;
14
- }, import("zod/v4/core").$strict>;
13
+ }, import("zod/v4/core").$strip>;
15
14
  readonly claims: import("zod").ZodObject<{
16
15
  resource: import("zod").ZodString;
17
16
  holder: import("zod").ZodString;
@@ -1,6 +1,6 @@
1
1
  /** SQLite tables and their record schemas; times are ISO 8601 UTC and credentials are hashed. */
2
2
  import { ActorRecord, AuditEntry, Claim } from "../../domain/model/index.js";
3
- export const SCHEMA_VERSION = 9;
3
+ export const SCHEMA_VERSION = 10;
4
4
  // Made on every open, not in a schema step, so an earlier build still opens the home:
5
5
  // an Actor's last write, and its writes on one record, which attention counts.
6
6
  export const AUDIT_INDEXES = `
@@ -16,8 +16,7 @@ CREATE TABLE actors (
16
16
  created_at TEXT NOT NULL,
17
17
  closed_at TEXT
18
18
  );
19
- CREATE UNIQUE INDEX one_root ON actors ((manager IS NULL)) WHERE manager IS NULL;
20
- CREATE UNIQUE INDEX handles_unique_among_siblings ON actors (manager, name) WHERE closed_at IS NULL;
19
+ CREATE UNIQUE INDEX handles_unique_among_open ON actors (name) WHERE closed_at IS NULL;
21
20
 
22
21
  -- Retired Actor-call history, retained privately; no operation reads it.
23
22
  CREATE TABLE calls (
@@ -61,7 +60,8 @@ CREATE TABLE audit (
61
60
  );
62
61
  `;
63
62
  export const TABLES = {
64
- actors: ActorRecord,
63
+ // The unused manager column stays in the file; an Actor row neither reads nor writes it.
64
+ actors: ActorRecord.strip(),
65
65
  claims: Claim,
66
66
  audit: AuditEntry,
67
67
  };
@@ -2,7 +2,7 @@
2
2
 
3
3
  Durable pair conversations, Work and asks, shared within the organization by active admitted Actor clients. Three separate clients expose identity, Messages, and Work tracking with its asks. Execution and viewer MCP servers share their records and authority rules. Work and ask list filters select matching records before pagination; business write roles remain unchanged.
4
4
 
5
- This package composes with Hub's canonical Actor authority and trusted admission, including the synchronous canonical relationship predicate described in the [adapter checkpoint](docs/hub-integration.md). [Changes since the last version 3 build](docs/hub-integration.md#changes-since-the-last-version-3-build) lists what changes for hosts. The runnable example uses model-free fixture identities; it is not production admission.
5
+ This package composes with Hub's canonical Actor authority and trusted admission, which names the person, as described in [Hub integration](docs/hub-integration.md). [Changes in 0.0.2](docs/hub-integration.md#changes-in-002) lists what changes for hosts. The runnable example uses model-free fixture identities; it is not production admission.
6
6
 
7
7
  ## Run
8
8
 
@@ -57,13 +57,13 @@ function workspace(authority: ActorAuthority, admittedActorID: string, databaseP
57
57
  }
58
58
  ```
59
59
 
60
- `ActorAuthority.get(callerID, actorID)` owns visibility and current canonical state. The required synchronous `.within(callerID, actorID, ancestorID): boolean` supplies inclusive retained ancestry, including closed members/anchors at every depth; unknown IDs return false. Bound `ActorsClient.within(actorID, ancestorID)` revalidates its actual active caller without resolving selected profiles or changing identity. The required `.manager(callerID, actorID): string | null` gives an Actor's retained manager: asks route through it, the root is the Actor with none, and a closed requester's or asker's rights pass to its nearest open manager. Hosts must implement both methods. The workspace stores no Actor rows, hierarchy or credentials. The optional root `bindWorkspace` factory performs the same explicit composition and returns three distinct clients. Contracts and errors are exported at `/contracts`; SDK server construction is exported at `/mcp`.
60
+ `ActorAuthority.get(callerID, actorID)` owns visibility and current canonical state. The required synchronous `.person(): string` names the person, the Actor that stands for the host's user: asks on Work their asker requested itself, or on no Work, go to it, and a closed Actor's rights pass to it. Bound `ActorsClient.person()` revalidates its actual active caller first. Every Actor sits on one flat level, and the workspace stores no Actor rows, relationships or credentials. The optional top-level `bindWorkspace` factory performs the same explicit composition and returns three distinct clients. Contracts and errors are exported at `/contracts`; SDK server construction is exported at `/mcp`.
61
61
 
62
- Lists return `{items, next, actors}`: the page, an opaque position for the next page (null at the end) and the names of the Actors it mentions. Work lists filter by status lists, one or several requesters, `updatedSince` and canonical subtrees (`ownerWithin`, `requesterWithin`), and go newest first with `order: "recent"`. Records carry `updatedAt`; history comes only from a get with `history: true`. See [lists](docs/contracts.md#lists).
62
+ Lists return `{items, next, actors}`: the page, an opaque position for the next page (null at the end) and the names of the Actors it mentions. Work lists filter by status lists, one or several owners or requesters and `updatedSince`, and go newest first with `order: "recent"`. Records carry `updatedAt`; history comes only from a get with `history: true`. See [lists](docs/contracts.md#lists).
63
63
 
64
- Work is the unit of progress: a title, progress, links and what it waits on, owned and requested by the caller unless it names another owner. Work an Actor requested itself completes when it submits; other Work waits for its requester's answer (`work_answer`). The requester may cancel open, active or changes-requested Work, and a closed requester's rights pass to its nearest open manager. See [Work](docs/contracts.md#work).
64
+ Work is the unit of progress: a title, progress, links and what it waits on, owned and requested by the caller unless it names another owner. Work an Actor requested itself completes when it submits; other Work waits for its requester's answer (`work_answer`). The requester may cancel open, active or changes-requested Work, and a closed requester's rights pass to the person. See [Work](docs/contracts.md#work).
65
65
 
66
- An ask is part of the Work its asker owns: `work_update {ticketID, ask}` adds a title, context, links and fields with options and a recommended choice. Any Actor answers it with `work_answer`, and the first answer resolves it unless it collects answers; only the asker's managers answer an approval. The Actor it is routed to, its asker's manager, may raise it one manager up. `work_get` returns Work with its asks, and `work_list` items carry their open asks. See [asks](docs/contracts.md#asks).
66
+ An ask is part of the Work its asker owns: `work_update {ticketID, ask}` adds a title, context, links and fields with options and a recommended choice. Any Actor answers it with `work_answer`, and the first answer resolves it unless it collects answers. An ask goes to its Work's requester, or to the person when its asker requested the Work itself or once the requester is closed, and only that Actor answers an approval, never its asker. `work_get` returns Work with its asks, and `work_list` items carry their open asks. See [asks](docs/contracts.md#asks).
67
67
 
68
68
  Each Message belongs to its pair's conversation (`conversationID`) and carries its place in the one Message order (`seq`). `messages.list({counterpartyID, before:"latest", limit:20})` returns the caller's conversation with that Actor, newest window ascending, with `older` for the page before it. `{between: [a, b]}` reads any pair's conversation the same way, and either form with `after` goes forward from a `next` position or the last `seq` a reader handled. `{of: actorID}` lists an Actor's conversations, newest first, each as its latest message. Every Actor reads every conversation. See [Messages](docs/contracts.md#messages).
69
69
 
@@ -8,5 +8,4 @@ export declare function fixtureAuthority(): {
8
8
  purpose: string;
9
9
  state: "active" | "closed";
10
10
  }>;
11
- managers: Map<string, string | null>;
12
11
  };
@@ -37,23 +37,10 @@ export function fixtureAuthority() {
37
37
  },
38
38
  ],
39
39
  ]);
40
- const managers = new Map([
41
- ["requester", null],
42
- ["owner", "requester"],
43
- ["respondent", "owner"],
44
- ["outsider", "requester"],
45
- ]);
40
+ // requester is the person, the Actor the host names as its user.
46
41
  const authority = {
47
42
  get: (_caller, id) => rows.get(id),
48
- manager: (_caller, actorID) => managers.get(actorID) ?? null,
49
- within: (_caller, actorID, ancestorID) => {
50
- if (!rows.has(ancestorID))
51
- return false;
52
- for (let at = actorID; at != null && rows.has(at); at = managers.get(at))
53
- if (at === ancestorID)
54
- return true;
55
- return false;
56
- },
43
+ person: () => "requester",
57
44
  };
58
- return { authority, rows, managers };
45
+ return { authority, rows };
59
46
  }
@@ -1,27 +1,27 @@
1
- /** Canonical identity and membership; the module stores no Actors or credentials. */
1
+ /** Canonical identity and the person; the module stores no Actors or credentials. */
2
2
  import { Actor, ActorQuery, type Input } from "../contracts/index.js";
3
3
  export interface ActorAuthority {
4
4
  /** Canonical visibility, including access to the admitted caller itself. */
5
5
  get(callerID: string, actorID: string): Actor | undefined;
6
- /** Inclusive retained ancestry, including closed members/anchors; unknown IDs return false. */
7
- within(callerID: string, actorID: string, ancestorID: string): boolean;
8
- /** The retained manager, closed or not; null for the root and for unknown IDs. */
9
- manager(callerID: string, actorID: string): string | null;
6
+ /**
7
+ * The person's Actor ID: it stands in for closed Actors and receives the asks on Work their asker
8
+ * requested itself.
9
+ */
10
+ person(): string;
10
11
  }
11
12
  export interface ActorsClient {
12
13
  get(input?: Input<typeof ActorQuery>): Actor;
13
- within(actorID: string, ancestorID: string): boolean;
14
- manager(actorID: string): string | null;
14
+ person(): string;
15
15
  }
16
16
  export declare function createActors(authority: ActorAuthority, callerID: string): ActorsClient;
17
17
  export declare function activeActor(actors: ActorsClient, actorID: string): Actor;
18
18
  /**
19
- * The Actor itself while it is open; once it is closed, its nearest open manager, or null. This
20
- * decides who holds rights, so an Actor the caller can't see is refused (`not_found`), never passed.
19
+ * The Actor itself while it is open; once it is closed, the person. This decides who holds rights,
20
+ * so an Actor the caller can't see is refused (`not_found`), never passed.
21
21
  */
22
- export declare function standIn(actors: ActorsClient, actorID: string): string | null;
22
+ export declare function standIn(actors: ActorsClient, actorID: string): string;
23
23
  /**
24
- * As standIn, for reads that route asks: an Actor the caller can't see counts as closed, so the walk
25
- * goes on past it. Never for authority.
24
+ * As standIn, for reads that route asks: an Actor the caller can't see counts as closed, so its asks
25
+ * go to the person. Never for authority.
26
26
  */
27
- export declare function routedStandIn(actors: ActorsClient, actorID: string): string | null;
27
+ export declare function routedStandIn(actors: ActorsClient, actorID: string): string;
@@ -1,4 +1,4 @@
1
- /** Canonical identity and membership; the module stores no Actors or credentials. */
1
+ /** Canonical identity and the person; the module stores no Actors or credentials. */
2
2
  import { Actor, ActorQuery, ID, parse, requireThat, WorkspaceError, } from "../contracts/index.js";
3
3
  export function createActors(authority, callerID) {
4
4
  function caller() {
@@ -16,16 +16,9 @@ export function createActors(authority, callerID) {
16
16
  requireThat(actor, "not_found", "Actor not found");
17
17
  return parse(Actor, actor);
18
18
  },
19
- within(actorID, ancestorID) {
20
- const member = parse(ID, actorID);
21
- const ancestor = parse(ID, ancestorID);
19
+ person() {
22
20
  caller();
23
- return authority.within(callerID, member, ancestor);
24
- },
25
- manager(actorID) {
26
- const member = parse(ID, actorID);
27
- caller();
28
- return authority.manager(callerID, member);
21
+ return parse(ID, authority.person());
29
22
  },
30
23
  };
31
24
  }
@@ -35,27 +28,18 @@ export function activeActor(actors, actorID) {
35
28
  return actor;
36
29
  }
37
30
  /**
38
- * The Actor itself while it is open; once it is closed, its nearest open manager, or null. This
39
- * decides who holds rights, so an Actor the caller can't see is refused (`not_found`), never passed.
31
+ * The Actor itself while it is open; once it is closed, the person. This decides who holds rights,
32
+ * so an Actor the caller can't see is refused (`not_found`), never passed.
40
33
  */
41
34
  export function standIn(actors, actorID) {
42
- return walk(actors, actorID, (at) => actors.get({ actorID: at }).state === "active");
35
+ return actors.get({ actorID }).state === "active" ? actorID : actors.person();
43
36
  }
44
37
  /**
45
- * As standIn, for reads that route asks: an Actor the caller can't see counts as closed, so the walk
46
- * goes on past it. Never for authority.
38
+ * As standIn, for reads that route asks: an Actor the caller can't see counts as closed, so its asks
39
+ * go to the person. Never for authority.
47
40
  */
48
41
  export function routedStandIn(actors, actorID) {
49
- return walk(actors, actorID, (at) => visible(actors, at)?.state === "active");
50
- }
51
- function walk(actors, actorID, open) {
52
- const seen = new Set();
53
- for (let at = actorID; at !== null && !seen.has(at); at = actors.manager(at)) {
54
- if (open(at))
55
- return at;
56
- seen.add(at);
57
- }
58
- return null;
42
+ return visible(actors, actorID)?.state === "active" ? actorID : actors.person();
59
43
  }
60
44
  function visible(actors, actorID) {
61
45
  try {
@@ -2,13 +2,6 @@ import { z } from "zod";
2
2
  export declare const ID: z.ZodString;
3
3
  export declare const Text: z.ZodString;
4
4
  export declare const Revision: z.ZodNumber;
5
- /** The smallest organization level that presents a contract; each level contains the one before. */
6
- export declare const Level: z.ZodEnum<{
7
- organization: "organization";
8
- report: "report";
9
- team: "team";
10
- }>;
11
- export type Level = z.infer<typeof Level>;
12
5
  export declare const Limit: z.ZodDefault<z.ZodNumber>;
13
6
  /** An opaque list position; it continues only the read and filter that issued it. */
14
7
  export declare const Next: z.ZodString;
@@ -61,8 +54,7 @@ export declare class WorkspaceError extends Error {
61
54
  /** Refuses input outside the schema, naming each issue as the Hub does. */
62
55
  export declare function parse<S extends z.ZodType>(schema: S, input: unknown): z.output<S>;
63
56
  export declare function requireThat(condition: unknown, code: WorkspaceError["code"], message: string): asserts condition;
64
- export declare function contract<S extends z.ZodType>(level: Level, description: string, input: S, output: z.ZodType, writes?: boolean): {
65
- level: "organization" | "report" | "team";
57
+ export declare function contract<S extends z.ZodType>(description: string, input: S, output: z.ZodType, writes?: boolean): {
66
58
  description: string;
67
59
  input: S;
68
60
  output: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
@@ -2,8 +2,6 @@ import { z } from "zod";
2
2
  export const ID = z.string().trim().min(1).max(200);
3
3
  export const Text = z.string().trim().min(1).max(20000);
4
4
  export const Revision = z.number().int().min(1).max(Number.MAX_SAFE_INTEGER);
5
- /** The smallest organization level that presents a contract; each level contains the one before. */
6
- export const Level = z.enum(["report", "team", "organization"]);
7
5
  export const Limit = z.number().int().min(1).max(100).default(50);
8
6
  /** An opaque list position; it continues only the read and filter that issued it. */
9
7
  export const Next = z.string().trim().min(1).max(1000);
@@ -49,8 +47,8 @@ export function requireThat(condition, code, message) {
49
47
  if (!condition)
50
48
  throw new WorkspaceError(code, message);
51
49
  }
52
- export function contract(level, description, input, output, writes = false) {
53
- return { level, description, input, output, writes };
50
+ export function contract(description, input, output, writes = false) {
51
+ return { description, input, output, writes };
54
52
  }
55
53
  export const listOf = (record) => z.strictObject({ items: z.array(record), next: Next.nullable(), actors: Actors });
56
54
  /** One ID or several, matching any of them. */
@@ -20,11 +20,6 @@ export declare const Response: z.ZodObject<{
20
20
  note: z.ZodOptional<z.ZodString>;
21
21
  at: z.ZodISODateTime;
22
22
  }, z.core.$strict>;
23
- /** Who passed an ask up, and when; a raise names no destination. */
24
- export declare const Raise: z.ZodObject<{
25
- actorID: z.ZodString;
26
- at: z.ZodISODateTime;
27
- }, z.core.$strict>;
28
23
  export declare const AskStatus: z.ZodEnum<{
29
24
  cancelled: "cancelled";
30
25
  open: "open";
@@ -67,10 +62,6 @@ export declare const FormState: z.ZodObject<{
67
62
  note: z.ZodOptional<z.ZodString>;
68
63
  at: z.ZodISODateTime;
69
64
  }, z.core.$strict>>;
70
- raises: z.ZodArray<z.ZodObject<{
71
- actorID: z.ZodString;
72
- at: z.ZodISODateTime;
73
- }, z.core.$strict>>;
74
65
  resolution: z.ZodOptional<z.ZodObject<{
75
66
  text: z.ZodString;
76
67
  reason: z.ZodOptional<z.ZodEnum<{
@@ -124,10 +115,6 @@ export declare const Form: z.ZodObject<{
124
115
  note: z.ZodOptional<z.ZodString>;
125
116
  at: z.ZodISODateTime;
126
117
  }, z.core.$strict>>;
127
- raises: z.ZodArray<z.ZodObject<{
128
- actorID: z.ZodString;
129
- at: z.ZodISODateTime;
130
- }, z.core.$strict>>;
131
118
  resolution: z.ZodOptional<z.ZodObject<{
132
119
  text: z.ZodString;
133
120
  reason: z.ZodOptional<z.ZodEnum<{
@@ -14,8 +14,6 @@ export const Response = z.strictObject({
14
14
  note: Text.optional(),
15
15
  at: z.iso.datetime(),
16
16
  });
17
- /** Who passed an ask up, and when; a raise names no destination. */
18
- export const Raise = z.strictObject({ actorID: ID, at: z.iso.datetime() });
19
17
  export const AskStatus = z.enum(["open", "resolved", "cancelled"]);
20
18
  /**
21
19
  * An ask, part of the Work its asker owns: open to any responder except where approval limits it.
@@ -35,7 +33,6 @@ export const FormState = z.strictObject({
35
33
  status: AskStatus,
36
34
  createdAt: z.iso.datetime(),
37
35
  responses: z.record(ID, Response),
38
- raises: z.array(Raise),
39
36
  resolution: z
40
37
  .strictObject({
41
38
  text: Text,
@@ -22,7 +22,6 @@ export type Message = z.infer<typeof Message>;
22
22
  export type MessageState = z.infer<typeof MessageState>;
23
23
  export declare const MessageContracts: {
24
24
  message_send: {
25
- level: "organization" | "report" | "team";
26
25
  description: string;
27
26
  input: z.ZodObject<{
28
27
  recipientID: z.ZodString;
@@ -32,7 +31,6 @@ export declare const MessageContracts: {
32
31
  writes: boolean;
33
32
  };
34
33
  message_get: {
35
- level: "organization" | "report" | "team";
36
34
  description: string;
37
35
  input: z.ZodObject<{
38
36
  messageID: z.ZodString;
@@ -41,7 +39,6 @@ export declare const MessageContracts: {
41
39
  writes: boolean;
42
40
  };
43
41
  message_list: {
44
- level: "organization" | "report" | "team";
45
42
  description: string;
46
43
  input: z.ZodObject<{
47
44
  between: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -21,9 +21,9 @@ export const Message = z.strictObject({
21
21
  });
22
22
  const messageID = z.strictObject({ messageID: ID });
23
23
  export const MessageContracts = {
24
- message_send: contract("report", "Send a message to another Actor; it joins the pair's conversation.", z.strictObject({ recipientID: ID, text: Text }), Message, true),
25
- message_get: contract("team", "Read a message.", messageID, Message),
26
- message_list: contract("report", "Name exactly one of between, counterpartyID or of. between (two Actors) or counterpartyID (the caller and one Actor) reads one conversation ascending: forward after a position or seq, or the newest window before latest/older. of lists an Actor's conversations, newest first, each as its latest message, continuing only after next.", z
24
+ message_send: contract("Send a message to another Actor; it joins the pair's conversation.", z.strictObject({ recipientID: ID, text: Text }), Message, true),
25
+ message_get: contract("Read a message.", messageID, Message),
26
+ message_list: contract("Name exactly one of between, counterpartyID or of. between (two Actors) or counterpartyID (the caller and one Actor) reads one conversation ascending: forward after a position or seq, or the newest window before latest/older. of lists an Actor's conversations, newest first, each as its latest message, continuing only after next.", z
27
27
  .strictObject({
28
28
  between: z.array(ID).length(2).optional(),
29
29
  counterpartyID: ID.optional(),