Skip to content

Commit ada2591

Browse files
committed
feat: interchange.grantRequirements for installer discovery
Static package.json + MEMORY_GRANT_REQUIREMENTS SSOT so host installers can learn memory:add / memory:search without executing the package.
1 parent 3451f08 commit ada2591

7 files changed

Lines changed: 149 additions & 3 deletions

File tree

‎docs/DISTILLER.md‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,19 @@ const { workflow, generatorAgentId } = createResidentDistiller({
9696

9797
## Grant manifest (process principal)
9898

99+
Installer discovery (not live grants):
100+
101+
- `package.json` → `interchange.grantRequirements`
102+
- typed SSOT: `MEMORY_GRANT_REQUIREMENTS` / `MEMORY_CAPABILITY_IDS` from
103+
`@corbits/memory` (or `@corbits/memory/tools`)
104+
105+
Minimum capabilities:
106+
99107
- `memory:add` (claim or note writes)
100108
- `memory:search` (corroboration + feed if using backfill)
101109

102-
Copy `accessTags` from the source onto claim writes — never mint broader tags.
110+
Deploy materializes these onto the workflow principal. Copy `accessTags` from
111+
the source onto claim writes — never mint broader tags.
103112

104113
## Out of scope
105114

‎package.json‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,21 @@
1010
"./distiller": "./src/distiller/index.ts"
1111
},
1212
"interchange": {
13-
"tools": "./src/tools/index.ts"
13+
"tools": "./src/tools/index.ts",
14+
"grantRequirements": [
15+
{
16+
"resource": "memory",
17+
"action": "add",
18+
"source": "tenant",
19+
"surfaces": ["tools", "distiller", "routes"]
20+
},
21+
{
22+
"resource": "memory",
23+
"action": "search",
24+
"source": "tenant",
25+
"surfaces": ["tools", "distiller", "routes"]
26+
}
27+
]
1428
},
1529
"license": "LGPL-2.1-only",
1630
"type": "module",

‎src/distiller/workflow.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { defineWorkflow, type WorkflowDefinition } from "@intx/workflow";
2323
import { memoryAdd } from "../tools/add.ts";
2424
import { memoryFeed } from "../tools/feed.ts";
2525
import { memorySearch } from "../tools/search.ts";
26+
import { MEMORY_CAPABILITY_IDS } from "../grant-requirements.ts";
2627
import {
2728
RESIDENT_DISTILLER_AGENT_ID,
2829
RESIDENT_DISTILLER_CRON_DEFAULT,
@@ -95,7 +96,8 @@ export function createResidentDistiller(
9596
"Resident memory distiller — feed → classify → claim write",
9697
systemPrompt: opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT,
9798
tools,
98-
capabilities: ["memory:search", "memory:add"],
99+
capabilities: [...MEMORY_CAPABILITY_IDS],
100+
99101
inference: opts.inference,
100102
tags: {
101103
role: "resident-distiller",

‎src/grant-requirements.test.ts‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { readFileSync } from "node:fs";
3+
import { join } from "node:path";
4+
5+
import {
6+
MEMORY_CAPABILITY_IDS,
7+
MEMORY_GRANT_REQUIREMENTS,
8+
} from "./grant-requirements.ts";
9+
10+
describe("MEMORY_GRANT_REQUIREMENTS", () => {
11+
test("covers add + search on memory resource", () => {
12+
expect(MEMORY_GRANT_REQUIREMENTS.map((r) => r.action).sort()).toEqual([
13+
"add",
14+
"search",
15+
]);
16+
for (const r of MEMORY_GRANT_REQUIREMENTS) {
17+
expect(r.resource).toBe("memory");
18+
expect(r.source).toBe("tenant");
19+
expect(r.surfaces).toContain("tools");
20+
expect(r.surfaces).toContain("distiller");
21+
}
22+
});
23+
24+
test("capability ids are resource:action", () => {
25+
expect([...MEMORY_CAPABILITY_IDS].sort()).toEqual([
26+
"memory:add",
27+
"memory:search",
28+
]);
29+
});
30+
31+
test("package.json interchange.grantRequirements stays in lockstep", () => {
32+
const pkg = JSON.parse(
33+
readFileSync(join(import.meta.dir, "..", "package.json"), "utf8"),
34+
) as {
35+
interchange?: {
36+
grantRequirements?: Array<{
37+
resource: string;
38+
action: string;
39+
source: string;
40+
surfaces: string[];
41+
}>;
42+
};
43+
};
44+
const fromPkg = pkg.interchange?.grantRequirements ?? [];
45+
expect(fromPkg).toEqual(
46+
MEMORY_GRANT_REQUIREMENTS.map((r) => ({
47+
resource: r.resource,
48+
action: r.action,
49+
source: r.source,
50+
surfaces: [...r.surfaces],
51+
})),
52+
);
53+
});
54+
});

‎src/grant-requirements.ts‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Grant *requirements* for installers — not live grants.
3+
*
4+
* Mirrored under `package.json` → `interchange.grantRequirements` so a
5+
* host installer can read npm metadata without executing code. The typed
6+
* export is the in-repo SSOT; keep package.json in lockstep.
7+
*
8+
* Shape matches Interchange definition grant requirements
9+
* (`resource` + `action` + `source`). Control plane materializes grants
10+
* onto the workflow principal at deploy/launch.
11+
*/
12+
13+
export type MemoryGrantSource = "tenant" | "creator" | "invoker";
14+
15+
/** Package surfaces that need the requirement when installed. */
16+
export type MemoryGrantSurface = "tools" | "distiller" | "routes";
17+
18+
export type MemoryGrantRequirement = {
19+
readonly resource: string;
20+
readonly action: string;
21+
/** Recommended authority source; installer/deploy may override. */
22+
readonly source: MemoryGrantSource;
23+
readonly surfaces: readonly MemoryGrantSurface[];
24+
};
25+
26+
/**
27+
* Minimum capability grants for memory tools / routes / process helpers.
28+
* Document-tag access (`memory.doc:…`, `memory.space:…`) is separate and
29+
* minted per document — not package install requirements.
30+
*/
31+
export const MEMORY_GRANT_REQUIREMENTS = [
32+
{
33+
resource: "memory",
34+
action: "add",
35+
source: "tenant",
36+
surfaces: ["tools", "distiller", "routes"],
37+
},
38+
{
39+
resource: "memory",
40+
action: "search",
41+
source: "tenant",
42+
surfaces: ["tools", "distiller", "routes"],
43+
},
44+
] as const satisfies readonly MemoryGrantRequirement[];
45+
46+
/** Compact `resource:action` form used on agent `capabilities` arrays. */
47+
export const MEMORY_CAPABILITY_IDS = MEMORY_GRANT_REQUIREMENTS.map(
48+
(r) => `${r.resource}:${r.action}` as const,
49+
);

‎src/index.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ export {
6262
LIST_LIMIT_MAX,
6363
} from "./memory.ts";
6464

65+
// Installer discovery — grant *requirements* (not live grants)
66+
export {
67+
MEMORY_CAPABILITY_IDS,
68+
MEMORY_GRANT_REQUIREMENTS,
69+
type MemoryGrantRequirement,
70+
type MemoryGrantSource,
71+
type MemoryGrantSurface,
72+
} from "./grant-requirements.ts";
73+
6574
// Ports — pluggable storage and live sources
6675
export type {
6776
DocumentStore,

‎src/tools/index.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ export {
1818
type MemoryToolEnv,
1919
} from "./client.ts";
2020

21+
/** Re-export installer grant requirements (same as package root). */
22+
export {
23+
MEMORY_CAPABILITY_IDS,
24+
MEMORY_GRANT_REQUIREMENTS,
25+
type MemoryGrantRequirement,
26+
type MemoryGrantSource,
27+
type MemoryGrantSurface,
28+
} from "../grant-requirements.ts";
29+

0 commit comments

Comments
 (0)