mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 06:47:28 +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
|
|
@ -120,15 +120,38 @@ describe("AgentStack coordinator", () => {
|
|||
it("records delegation grants and emits events", () => {
|
||||
const stack = new AgentStack();
|
||||
const listener = vi.fn();
|
||||
stack.on(listener);
|
||||
stack.registerAgent(agent);
|
||||
const grant = stack.delegate(owner, agent.did, ["tasks:create"]);
|
||||
|
||||
expect(grant.ownerDid).toBe(owner);
|
||||
expect(grant.agentDid).toBe(agent.did);
|
||||
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: "agent.registered" }));
|
||||
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: "delegation.granted" }));
|
||||
});
|
||||
stack.on(listener);
|
||||
stack.registerAgent(agent);
|
||||
const grant = stack.delegate(owner, agent.did, ["tasks:create"]);
|
||||
|
||||
expect(grant.ownerDid).toBe(owner);
|
||||
expect(grant.agentDid).toBe(agent.did);
|
||||
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: "agent.registered" }));
|
||||
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", () => {
|
||||
const stack = new AgentStack();
|
||||
|
|
|
|||
|
|
@ -178,18 +178,46 @@ export class AgentStack {
|
|||
return grant;
|
||||
}
|
||||
|
||||
revokeDelegation(grantId: string): DelegationGrant {
|
||||
const grant = this.delegations.get(grantId);
|
||||
if (!grant) throw new Error(`Unknown delegation grant: ${grantId}`);
|
||||
this.delegations.delete(grantId);
|
||||
this.emit({ type: "delegation.revoked", grant });
|
||||
return grant;
|
||||
}
|
||||
|
||||
listTasks(filter?: { ownerDid?: string; assigneeDid?: string; status?: TaskStatus }): DidTask[] {
|
||||
return [...this.tasks.values()].filter((task) => {
|
||||
if (filter?.ownerDid && task.ownerDid !== filter.ownerDid) return false;
|
||||
if (filter?.assigneeDid && task.assigneeDid !== filter.assigneeDid) return false;
|
||||
revokeDelegation(grantId: string): DelegationGrant {
|
||||
const grant = this.delegations.get(grantId);
|
||||
if (!grant) throw new Error(`Unknown delegation grant: ${grantId}`);
|
||||
this.delegations.delete(grantId);
|
||||
this.emit({ type: "delegation.revoked", 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[] {
|
||||
return [...this.tasks.values()].filter((task) => {
|
||||
if (filter?.ownerDid && task.ownerDid !== filter.ownerDid) return false;
|
||||
if (filter?.assigneeDid && task.assigneeDid !== filter.assigneeDid) return false;
|
||||
if (filter?.status && task.status !== filter.status) return false;
|
||||
return true;
|
||||
});
|
||||
|
|
@ -205,10 +233,18 @@ export class AgentStack {
|
|||
|
||||
private requireTask(id: string): DidTask {
|
||||
const task = this.tasks.get(id);
|
||||
if (!task) throw new Error(`Unknown task: ${id}`);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
if (!task) throw new Error(`Unknown task: ${id}`);
|
||||
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. */
|
||||
export const agentStackPlugin: PluginDefinition = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue