mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
feat(agentstack): add delegation authority checks
This commit is contained in:
parent
edf32139ae
commit
61d42e4aaf
4 changed files with 92 additions and 27 deletions
|
|
@ -74,7 +74,8 @@ Backed by `src/index.ts`, `src/types.ts`, `src/manifest.ts`:
|
||||||
- **Status lifecycle** — `pending → queued → running → blocked → complete | failed | cancelled`.
|
- **Status lifecycle** — `pending → queued → running → blocked → complete | failed | cancelled`.
|
||||||
- **`AgentStack` coordinator** (in-memory `Map` storage, injectable clock):
|
- **`AgentStack` coordinator** (in-memory `Map` storage, injectable clock):
|
||||||
`registerAgent`, `getAgent`, `createTask`, `getTask`, `assignTask`, `updateTaskStatus`,
|
`registerAgent`, `getAgent`, `createTask`, `getTask`, `assignTask`, `updateTaskStatus`,
|
||||||
`delegate`, `revokeDelegation`, `listTasks`, `snapshot`, and an `on(listener)` event subscription.
|
`delegate`, `revokeDelegation`, `listDelegations`, `hasDelegation`, `listTasks`, `snapshot`,
|
||||||
|
and an `on(listener)` event subscription.
|
||||||
- **Events** — `agent.registered`, `task.created`, `task.assigned`, `task.updated`,
|
- **Events** — `agent.registered`, `task.created`, `task.assigned`, `task.updated`,
|
||||||
`delegation.granted`, `delegation.revoked`.
|
`delegation.granted`, `delegation.revoked`.
|
||||||
- **`agentStackPlugin`** — a LogicSRC `PluginDefinition`, plus `agentStackManifest` declaring
|
- **`agentStackPlugin`** — a LogicSRC `PluginDefinition`, plus `agentStackManifest` declaring
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ Shared AppKit OpenSpec (`profullstack-web/openspec/specs/agentstack`).
|
||||||
(`pending → queued → running → blocked → complete | failed | cancelled`) that can bind to
|
(`pending → queued → running → blocked → complete | failed | cancelled`) that can bind to
|
||||||
payment, escrow, and reputation events.
|
payment, escrow, and reputation events.
|
||||||
- **`AgentStack`** — an in-memory coordinator that registers agents, tracks tasks, records
|
- **`AgentStack`** — an in-memory coordinator that registers agents, tracks tasks, records
|
||||||
delegation grants, and emits coordination events. Storage backends can wrap the same API.
|
delegation grants, checks active scoped authority, and emits coordination events. Storage
|
||||||
|
backends can wrap the same API.
|
||||||
- **`agentStackPlugin`** — a LogicSRC `PluginDefinition` (validated against the plugin
|
- **`agentStackPlugin`** — a LogicSRC `PluginDefinition` (validated against the plugin
|
||||||
manifest schema) exposing AgentStack as a coordination plugin with routes, events,
|
manifest schema) exposing AgentStack as a coordination plugin with routes, events,
|
||||||
permissions, and a TUI panel.
|
permissions, and a TUI panel.
|
||||||
|
|
@ -39,6 +40,10 @@ const task = stack.createTask({ ownerDid: owner, sourceApp: "sh1pt.com", title:
|
||||||
stack.assignTask(task.id, agentDid("abc"));
|
stack.assignTask(task.id, agentDid("abc"));
|
||||||
stack.updateTaskStatus(task.id, "running");
|
stack.updateTaskStatus(task.id, "running");
|
||||||
stack.updateTaskStatus(task.id, "complete", { reputationEventId: "rep_1" });
|
stack.updateTaskStatus(task.id, "complete", { reputationEventId: "rep_1" });
|
||||||
|
|
||||||
|
const grant = stack.delegate(owner, agentDid("abc"), ["tasks:create"]);
|
||||||
|
stack.hasDelegation(owner, agentDid("abc"), "tasks:create"); // true while the grant is active
|
||||||
|
stack.revokeDelegation(grant.id);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cross-app identity
|
## Cross-app identity
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,29 @@ describe("AgentStack coordinator", () => {
|
||||||
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: "delegation.granted" }));
|
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: "delegation.granted" }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("checks active delegation authority by owner, agent, scope, and expiry", () => {
|
||||||
|
const stack = new AgentStack(() => "2026-01-01T00:00:00.000Z");
|
||||||
|
stack.registerAgent(agent);
|
||||||
|
|
||||||
|
stack.delegate(owner, agent.did, ["tasks:create"], "2026-01-02T00:00:00.000Z");
|
||||||
|
|
||||||
|
expect(stack.hasDelegation(owner, agent.did, "tasks:create")).toBe(true);
|
||||||
|
expect(stack.hasDelegation(owner, agent.did, "tasks:update")).toBe(false);
|
||||||
|
expect(stack.hasDelegation(owner, agent.did, "tasks:create", "2026-01-03T00:00:00.000Z")).toBe(false);
|
||||||
|
expect(stack.listDelegations({ ownerDid: owner, agentDid: agent.did, scope: "tasks:create" })).toHaveLength(1);
|
||||||
|
expect(stack.listDelegations({ activeAt: "2026-01-03T00:00:00.000Z" })).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows wildcard delegation scopes and ignores revoked grants", () => {
|
||||||
|
const stack = new AgentStack();
|
||||||
|
stack.registerAgent(agent);
|
||||||
|
const grant = stack.delegate(owner, agent.did, ["*"]);
|
||||||
|
|
||||||
|
expect(stack.hasDelegation(owner, agent.did, "tasks:update")).toBe(true);
|
||||||
|
stack.revokeDelegation(grant.id);
|
||||||
|
expect(stack.hasDelegation(owner, agent.did, "tasks:update")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects unknown agents and invalid DIDs", () => {
|
it("rejects unknown agents and invalid DIDs", () => {
|
||||||
const stack = new AgentStack();
|
const stack = new AgentStack();
|
||||||
expect(() => stack.createTask({ ownerDid: "nope", sourceApp: "x", title: "t" })).toThrow();
|
expect(() => stack.createTask({ ownerDid: "nope", sourceApp: "x", title: "t" })).toThrow();
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,34 @@ export class AgentStack {
|
||||||
return grant;
|
return grant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listDelegations(filter?: {
|
||||||
|
ownerDid?: string;
|
||||||
|
agentDid?: string;
|
||||||
|
scope?: string;
|
||||||
|
activeAt?: string;
|
||||||
|
}): DelegationGrant[] {
|
||||||
|
return [...this.delegations.values()].filter((grant) => {
|
||||||
|
if (filter?.ownerDid && grant.ownerDid !== filter.ownerDid) return false;
|
||||||
|
if (filter?.agentDid && grant.agentDid !== filter.agentDid) return false;
|
||||||
|
if (filter?.scope && !this.grantAllowsScope(grant, filter.scope)) return false;
|
||||||
|
if (filter?.activeAt && !this.grantIsActive(grant, filter.activeAt)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDelegation(ownerDidValue: string, agentDidValue: string, scope: string, at: string = this.now()): boolean {
|
||||||
|
if (!parseDid(ownerDidValue)) throw new Error(`Invalid owner DID: ${ownerDidValue}`);
|
||||||
|
if (!this.agents.has(agentDidValue)) throw new Error(`Unknown agent: ${agentDidValue}`);
|
||||||
|
return (
|
||||||
|
this.listDelegations({
|
||||||
|
ownerDid: ownerDidValue,
|
||||||
|
agentDid: agentDidValue,
|
||||||
|
scope,
|
||||||
|
activeAt: at
|
||||||
|
}).length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
listTasks(filter?: { ownerDid?: string; assigneeDid?: string; status?: TaskStatus }): DidTask[] {
|
listTasks(filter?: { ownerDid?: string; assigneeDid?: string; status?: TaskStatus }): DidTask[] {
|
||||||
return [...this.tasks.values()].filter((task) => {
|
return [...this.tasks.values()].filter((task) => {
|
||||||
if (filter?.ownerDid && task.ownerDid !== filter.ownerDid) return false;
|
if (filter?.ownerDid && task.ownerDid !== filter.ownerDid) return false;
|
||||||
|
|
@ -208,6 +236,14 @@ export class AgentStack {
|
||||||
if (!task) throw new Error(`Unknown task: ${id}`);
|
if (!task) throw new Error(`Unknown task: ${id}`);
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private grantAllowsScope(grant: DelegationGrant, scope: string): boolean {
|
||||||
|
return grant.scopes.includes(scope) || grant.scopes.includes("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
private grantIsActive(grant: DelegationGrant, at: string): boolean {
|
||||||
|
return !grant.expiresAt || grant.expiresAt > at;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** LogicSRC plugin definition exposing AgentStack as a coordination plugin. */
|
/** LogicSRC plugin definition exposing AgentStack as a coordination plugin. */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue