@intx/hub-sessions 0.1.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.
@@ -0,0 +1,247 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ agentStateAuthorize,
4
+ agentStateKindHandler,
5
+ AGENT_STATE_DEPLOY_REF,
6
+ } from "./agent-state-kind";
7
+ import type { Principal, RepoId } from "./repo-store";
8
+
9
+ describe("agentStateKindHandler metadata", () => {
10
+ test("declares the agent-state kind and agents directory prefix", () => {
11
+ expect(agentStateKindHandler.kind).toBe("agent-state");
12
+ expect(agentStateKindHandler.directoryPrefix).toBe("agents");
13
+ });
14
+ });
15
+
16
+ describe("agentStateAuthorize", () => {
17
+ const REPO: RepoId = { kind: "agent-state", id: "agent-1" };
18
+ const OTHER_REPO: RepoId = { kind: "agent-state", id: "agent-2" };
19
+ const STATE_REF = "refs/heads/state";
20
+
21
+ function farFuture(): number {
22
+ return Date.now() + 60_000;
23
+ }
24
+
25
+ function userPrincipal(
26
+ overrides: {
27
+ effect?: "allow" | "deny";
28
+ resource?: string;
29
+ grantVerb?: string;
30
+ refPattern?: string;
31
+ actions?: string[];
32
+ expiresAt?: number;
33
+ } = {},
34
+ ): Principal {
35
+ return {
36
+ kind: "user",
37
+ principalId: "user-1",
38
+ tenantId: "tenant-1",
39
+ authz: {
40
+ effect: overrides.effect ?? "allow",
41
+ resource: overrides.resource ?? "agent-state:agent-1",
42
+ grantVerb: overrides.grantVerb ?? "read",
43
+ },
44
+ tokenClaims: {
45
+ refPattern: overrides.refPattern ?? "refs/heads/**",
46
+ actions: overrides.actions ?? ["createPack", "resolveRef"],
47
+ expiresAt: overrides.expiresAt ?? farFuture(),
48
+ },
49
+ } as Principal;
50
+ }
51
+
52
+ test("hub principal: allowed for any action (regression)", () => {
53
+ for (const action of [
54
+ "init",
55
+ "writeTree",
56
+ "receivePack",
57
+ "createPack",
58
+ "resolveRef",
59
+ ] as const) {
60
+ const r = agentStateAuthorize(
61
+ { kind: "hub" } as Principal,
62
+ REPO,
63
+ STATE_REF,
64
+ action,
65
+ );
66
+ expect(r.allowed).toBe(true);
67
+ }
68
+ });
69
+
70
+ test("sidecar principal: receivePack allowed on its own repo (regression)", () => {
71
+ const sidecar = { kind: "sidecar", agentId: "agent-1" } as Principal;
72
+ const r = agentStateAuthorize(sidecar, REPO, STATE_REF, "receivePack");
73
+ expect(r.allowed).toBe(true);
74
+ });
75
+
76
+ test("sidecar principal: createPack / resolveRef on deploy ref allowed (regression)", () => {
77
+ const sidecar = { kind: "sidecar", agentId: "agent-1" } as Principal;
78
+ expect(
79
+ agentStateAuthorize(sidecar, REPO, AGENT_STATE_DEPLOY_REF, "createPack")
80
+ .allowed,
81
+ ).toBe(true);
82
+ expect(
83
+ agentStateAuthorize(sidecar, REPO, AGENT_STATE_DEPLOY_REF, "resolveRef")
84
+ .allowed,
85
+ ).toBe(true);
86
+ });
87
+
88
+ test("sidecar principal: createPack on non-deploy ref denied (regression)", () => {
89
+ const sidecar = { kind: "sidecar", agentId: "agent-1" } as Principal;
90
+ const r = agentStateAuthorize(sidecar, REPO, STATE_REF, "createPack");
91
+ expect(r.allowed).toBe(false);
92
+ if (r.allowed) throw new Error("unreachable");
93
+ expect(r.reason).toMatch(/sidecar may only fetch/);
94
+ });
95
+
96
+ test("sidecar principal: cannot access another sidecar's repo (regression)", () => {
97
+ const sidecar = { kind: "sidecar", agentId: "agent-1" } as Principal;
98
+ const r = agentStateAuthorize(
99
+ sidecar,
100
+ OTHER_REPO,
101
+ STATE_REF,
102
+ "receivePack",
103
+ );
104
+ expect(r.allowed).toBe(false);
105
+ if (r.allowed) throw new Error("unreachable");
106
+ expect(r.reason).toMatch(/cannot access/);
107
+ });
108
+
109
+ test("sidecar principal: writeTree denied (regression)", () => {
110
+ const sidecar = { kind: "sidecar", agentId: "agent-1" } as Principal;
111
+ const r = agentStateAuthorize(sidecar, REPO, STATE_REF, "writeTree");
112
+ expect(r.allowed).toBe(false);
113
+ if (r.allowed) throw new Error("unreachable");
114
+ expect(r.reason).toMatch(/hub-only/);
115
+ });
116
+
117
+ test("user principal: allowed when claims and verdict agree", () => {
118
+ const r = agentStateAuthorize(
119
+ userPrincipal(),
120
+ REPO,
121
+ STATE_REF,
122
+ "createPack",
123
+ );
124
+ expect(r.allowed).toBe(true);
125
+ });
126
+
127
+ test("user principal: denied for non-agent-state repo", () => {
128
+ const r = agentStateAuthorize(
129
+ userPrincipal({ resource: "asset:asset-1" }),
130
+ { kind: "skill", id: "asset-1" },
131
+ STATE_REF,
132
+ "createPack",
133
+ );
134
+ expect(r.allowed).toBe(false);
135
+ if (r.allowed) throw new Error("unreachable");
136
+ expect(r.reason).toMatch(/non-agent-state repo/);
137
+ });
138
+
139
+ test("user principal: malformed principal is denied with structural reason", () => {
140
+ const badPrincipal = {
141
+ kind: "user",
142
+ principalId: "user-1",
143
+ // missing tenantId, authz, tokenClaims
144
+ } as Principal;
145
+ const r = agentStateAuthorize(badPrincipal, REPO, STATE_REF, "createPack");
146
+ expect(r.allowed).toBe(false);
147
+ if (r.allowed) throw new Error("unreachable");
148
+ expect(r.reason).toMatch(/user principal is malformed/);
149
+ });
150
+
151
+ test("user principal: denied when tokenClaims.actions does not include the requested action", () => {
152
+ const r = agentStateAuthorize(
153
+ userPrincipal({ actions: ["resolveRef"] }),
154
+ REPO,
155
+ STATE_REF,
156
+ "createPack",
157
+ );
158
+ expect(r.allowed).toBe(false);
159
+ if (r.allowed) throw new Error("unreachable");
160
+ expect(r.reason).toMatch(/token does not grant action createPack/);
161
+ });
162
+
163
+ test("user principal: denied when refPattern does not match the requested ref", () => {
164
+ const r = agentStateAuthorize(
165
+ userPrincipal({ refPattern: "refs/heads/release-*" }),
166
+ REPO,
167
+ STATE_REF,
168
+ "createPack",
169
+ );
170
+ expect(r.allowed).toBe(false);
171
+ if (r.allowed) throw new Error("unreachable");
172
+ expect(r.reason).toMatch(/refPattern .* does not match/);
173
+ });
174
+
175
+ test("user principal: denied when the token is expired", () => {
176
+ const r = agentStateAuthorize(
177
+ userPrincipal({ expiresAt: Date.now() - 1 }),
178
+ REPO,
179
+ STATE_REF,
180
+ "createPack",
181
+ );
182
+ expect(r.allowed).toBe(false);
183
+ if (r.allowed) throw new Error("unreachable");
184
+ expect(r.reason).toMatch(/token expired/);
185
+ });
186
+
187
+ test("user principal: denied when verdict.resource does not target this repo (sanity drift)", () => {
188
+ const r = agentStateAuthorize(
189
+ userPrincipal({ resource: "agent-state:agent-other" }),
190
+ REPO,
191
+ STATE_REF,
192
+ "createPack",
193
+ );
194
+ expect(r.allowed).toBe(false);
195
+ if (r.allowed) throw new Error("unreachable");
196
+ expect(r.reason).toMatch(/authz verdict resource .* does not match/);
197
+ });
198
+
199
+ test("user principal: denied when verdict.resource has the wrong kind prefix (sanity drift)", () => {
200
+ const r = agentStateAuthorize(
201
+ userPrincipal({ resource: "asset:agent-1" }),
202
+ REPO,
203
+ STATE_REF,
204
+ "createPack",
205
+ );
206
+ expect(r.allowed).toBe(false);
207
+ if (r.allowed) throw new Error("unreachable");
208
+ expect(r.reason).toMatch(/authz verdict resource .* does not match/);
209
+ });
210
+
211
+ test("user principal: denied when verdict.grantVerb does not match the action's verb (sanity drift)", () => {
212
+ const r = agentStateAuthorize(
213
+ userPrincipal({ grantVerb: "write" }),
214
+ REPO,
215
+ STATE_REF,
216
+ "createPack",
217
+ );
218
+ expect(r.allowed).toBe(false);
219
+ if (r.allowed) throw new Error("unreachable");
220
+ expect(r.reason).toMatch(/authz verdict grantVerb .* does not match/);
221
+ });
222
+
223
+ test("user principal: denied when verdict effect is deny even though all sanity checks pass", () => {
224
+ const r = agentStateAuthorize(
225
+ userPrincipal({ effect: "deny" }),
226
+ REPO,
227
+ STATE_REF,
228
+ "createPack",
229
+ );
230
+ expect(r.allowed).toBe(false);
231
+ if (r.allowed) throw new Error("unreachable");
232
+ expect(r.reason).toMatch(/authz verdict denied/);
233
+ });
234
+
235
+ test("user principal: write action requires write grantVerb and matching claims", () => {
236
+ const r = agentStateAuthorize(
237
+ userPrincipal({
238
+ actions: ["receivePack"],
239
+ grantVerb: "write",
240
+ }),
241
+ REPO,
242
+ STATE_REF,
243
+ "receivePack",
244
+ );
245
+ expect(r.allowed).toBe(true);
246
+ });
247
+ });
@@ -0,0 +1,204 @@
1
+ import { type } from "arktype";
2
+ import { glob, repoActionToGrantVerb } from "@intx/hub-common";
3
+ import {
4
+ UserPrincipal,
5
+ type AuthorizeFn,
6
+ type KindHandler,
7
+ type Principal,
8
+ type ValidatePushResult,
9
+ } from "./repo-store";
10
+
11
+ export type AgentStateHubPrincipal = { readonly kind: "hub" };
12
+
13
+ export type AgentStateSidecarPrincipal = {
14
+ readonly kind: "sidecar";
15
+ readonly agentId: string;
16
+ };
17
+
18
+ export type AgentStatePrincipal =
19
+ | AgentStateHubPrincipal
20
+ | AgentStateSidecarPrincipal;
21
+
22
+ const SidecarPrincipal = type({
23
+ kind: "'sidecar'",
24
+ agentId: "string",
25
+ });
26
+
27
+ export const AGENT_STATE_DEPLOY_REF = "refs/heads/deploy";
28
+
29
+ const ALLOWED_STATE_TOP_LEVEL = new Set([
30
+ "state",
31
+ ".gitignore",
32
+ "turns.jsonl",
33
+ "prompt.jsonl",
34
+ "response.jsonl",
35
+ "manifest.jsonl",
36
+ "metadata.json",
37
+ "tool-output",
38
+ ]);
39
+
40
+ const ALLOWED_DEPLOY_TOP_LEVEL = new Set(["deploy", ".gitignore"]);
41
+
42
+ export const agentStateKindHandler: KindHandler = {
43
+ kind: "agent-state",
44
+ directoryPrefix: "agents",
45
+ validatePush: ({ ref, topLevelTreePaths }): ValidatePushResult => {
46
+ // The deploy ref carries hub-authored prompt content under
47
+ // `deploy/`; every other ref carries sidecar-pushed agent state
48
+ // and must stay confined to the state-bearing allowlist.
49
+ const allowed =
50
+ ref === AGENT_STATE_DEPLOY_REF
51
+ ? ALLOWED_DEPLOY_TOP_LEVEL
52
+ : ALLOWED_STATE_TOP_LEVEL;
53
+ const offender = topLevelTreePaths.find((p) => !allowed.has(p));
54
+ if (offender !== undefined) {
55
+ return {
56
+ ok: false,
57
+ reason: `tree contains disallowed top-level path: ${offender}`,
58
+ };
59
+ }
60
+ if (!topLevelTreePaths.some((p) => p !== ".gitignore")) {
61
+ return {
62
+ ok: false,
63
+ reason: "tree must include at least one state-bearing top-level entry",
64
+ };
65
+ }
66
+ return { ok: true };
67
+ },
68
+ onRefUpdated: () => {
69
+ // No consumer at this kind today; the substrate's hook surface is
70
+ // uniform across kinds and future kinds will use it.
71
+ },
72
+ };
73
+
74
+ export const agentStateAuthorize: AuthorizeFn = (
75
+ principal: Principal,
76
+ repoId,
77
+ ref,
78
+ action,
79
+ ) => {
80
+ if (principal.kind === "hub") {
81
+ // Full access at this kind. Hub-side reads (getDeployRef,
82
+ // createDeployPack) and writes (writeDeployTree) all flow through
83
+ // here, so changing this branch tightens behavior in non-obvious
84
+ // places.
85
+ return { allowed: true };
86
+ }
87
+
88
+ if (principal.kind === "sidecar") {
89
+ const parsed = SidecarPrincipal(principal);
90
+ if (parsed instanceof type.errors) {
91
+ return {
92
+ allowed: false,
93
+ reason: `sidecar principal is malformed: ${parsed.summary}`,
94
+ };
95
+ }
96
+ if (repoId.kind !== "agent-state" || repoId.id !== parsed.agentId) {
97
+ return {
98
+ allowed: false,
99
+ reason: `sidecar ${parsed.agentId} cannot access ${repoId.kind}/${repoId.id}`,
100
+ };
101
+ }
102
+ switch (action) {
103
+ case "receivePack":
104
+ case "resolveRef":
105
+ return { allowed: true };
106
+ case "createPack":
107
+ if (ref !== AGENT_STATE_DEPLOY_REF) {
108
+ return {
109
+ allowed: false,
110
+ reason: `sidecar may only fetch ${AGENT_STATE_DEPLOY_REF}, not ${ref}`,
111
+ };
112
+ }
113
+ return { allowed: true };
114
+ case "init":
115
+ return {
116
+ allowed: false,
117
+ reason: "init is not authorize-gated for agent-state",
118
+ };
119
+ case "writeTree":
120
+ return {
121
+ allowed: false,
122
+ reason: `action ${action} is hub-only for agent-state`,
123
+ };
124
+ default: {
125
+ const _exhaustive: never = action;
126
+ return {
127
+ allowed: false,
128
+ reason: `unhandled action: ${String(_exhaustive)}`,
129
+ };
130
+ }
131
+ }
132
+ }
133
+
134
+ if (principal.kind === "user") {
135
+ // The route layer has already pre-resolved the grant verdict and
136
+ // attached it as `authz`. The substrate does NOT re-query the
137
+ // grant store here; it (a) checks the bearer-token's claims
138
+ // bound the requested (ref, action) and have not expired, and
139
+ // (b) sanity-checks that the pre-resolved verdict targets this
140
+ // exact resource and grant verb. Both gates must pass before the
141
+ // verdict's `effect` is honoured.
142
+ const parsed = UserPrincipal(principal);
143
+ if (parsed instanceof type.errors) {
144
+ return {
145
+ allowed: false,
146
+ reason: `user principal is malformed: ${parsed.summary}`,
147
+ };
148
+ }
149
+ if (repoId.kind !== "agent-state") {
150
+ return {
151
+ allowed: false,
152
+ reason: `user authorize received non-agent-state repo ${repoId.kind}/${repoId.id}`,
153
+ };
154
+ }
155
+ if (!parsed.tokenClaims.actions.includes(action)) {
156
+ return {
157
+ allowed: false,
158
+ reason: `token does not grant action ${action}`,
159
+ };
160
+ }
161
+ // `ref === "*"` is the substrate's sentinel for the bulk read
162
+ // performed by `listRefs`. Per-ref filtering is the advertise-refs
163
+ // layer's responsibility, so the bulk read is gated on action and
164
+ // expiry alone.
165
+ if (ref !== "*" && !glob.match(parsed.tokenClaims.refPattern, ref)) {
166
+ return {
167
+ allowed: false,
168
+ reason: `token refPattern ${parsed.tokenClaims.refPattern} does not match ${ref}`,
169
+ };
170
+ }
171
+ if (Date.now() >= parsed.tokenClaims.expiresAt) {
172
+ return {
173
+ allowed: false,
174
+ reason: `token expired at ${parsed.tokenClaims.expiresAt}`,
175
+ };
176
+ }
177
+ const expectedResource = `agent-state:${repoId.id}`;
178
+ if (parsed.authz.resource !== expectedResource) {
179
+ return {
180
+ allowed: false,
181
+ reason: `authz verdict resource ${parsed.authz.resource} does not match ${expectedResource}`,
182
+ };
183
+ }
184
+ const expectedGrantVerb = repoActionToGrantVerb(action);
185
+ if (parsed.authz.grantVerb !== expectedGrantVerb) {
186
+ return {
187
+ allowed: false,
188
+ reason: `authz verdict grantVerb ${parsed.authz.grantVerb} does not match ${expectedGrantVerb}`,
189
+ };
190
+ }
191
+ if (parsed.authz.effect === "allow") {
192
+ return { allowed: true };
193
+ }
194
+ return {
195
+ allowed: false,
196
+ reason: `authz verdict denied for ${expectedResource} ${expectedGrantVerb}`,
197
+ };
198
+ }
199
+
200
+ return {
201
+ allowed: false,
202
+ reason: `unknown principal kind: ${principal.kind}`,
203
+ };
204
+ };