@ouro.bot/friends 0.1.0-alpha.5 → 0.1.0-alpha.7
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.
- package/README.md +65 -16
- package/changelog.json +12 -0
- package/dist/a2a-client/index.d.ts +1 -0
- package/dist/a2a-client/index.js +5 -1
- package/dist/a2a-client/roster-verify.d.ts +15 -0
- package/dist/a2a-client/roster-verify.js +61 -0
- package/dist/account-roster.d.ts +52 -0
- package/dist/account-roster.js +108 -0
- package/dist/agent-peer.js +5 -1
- package/dist/audit.d.ts +42 -0
- package/dist/audit.js +86 -0
- package/dist/connect-authority.d.ts +43 -0
- package/dist/connect-authority.js +84 -0
- package/dist/connect.d.ts +55 -0
- package/dist/connect.js +160 -0
- package/dist/coordination.d.ts +17 -1
- package/dist/coordination.js +80 -6
- package/dist/file-bundle.d.ts +5 -0
- package/dist/file-bundle.js +4 -0
- package/dist/friend-lookup.d.ts +9 -0
- package/dist/friend-lookup.js +69 -0
- package/dist/identity.d.ts +17 -0
- package/dist/identity.js +68 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +51 -2
- package/dist/mailbox/index.d.ts +13 -10
- package/dist/mailbox/index.js +1 -1
- package/dist/mcp/dispatch.d.ts +27 -1
- package/dist/mcp/dispatch.js +110 -3
- package/dist/mcp/run-main.js +8 -5
- package/dist/mcp/schemas.js +51 -4
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +2 -2
- package/dist/mission-result.d.ts +82 -0
- package/dist/mission-result.js +200 -0
- package/dist/mission-store-file.js +8 -0
- package/dist/resolver.d.ts +32 -1
- package/dist/resolver.js +50 -3
- package/dist/roster-store-file.d.ts +16 -0
- package/dist/roster-store-file.js +125 -0
- package/dist/roster-store-memory.d.ts +9 -0
- package/dist/roster-store-memory.js +20 -0
- package/dist/roster-store.d.ts +29 -0
- package/dist/roster-store.js +9 -0
- package/dist/roster-verifier.d.ts +23 -0
- package/dist/roster-verifier.js +47 -0
- package/dist/trust-explanation.d.ts +7 -1
- package/dist/trust-explanation.js +52 -34
- package/dist/trust-mutation.d.ts +13 -1
- package/dist/trust-mutation.js +31 -2
- package/dist/types.d.ts +64 -0
- package/package.json +2 -1
package/dist/identity.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveAgentIdentity = resolveAgentIdentity;
|
|
4
|
+
exports.withMigratedIdentity = withMigratedIdentity;
|
|
5
|
+
// Agent identity migrate-on-read (p11 Item 2 — the DID re-key).
|
|
6
|
+
//
|
|
7
|
+
// The durable identity home is `AgentMeta.identity` ({ did, pinnedKey?, handle?,
|
|
8
|
+
// pinnedAt? }). Legacy records carry only the optional `a2a.did` hint (or nothing).
|
|
9
|
+
// `resolveAgentIdentity` reads either, preferring the durable home and lifting
|
|
10
|
+
// `a2a.did` on a miss (migrate-on-read), mirroring FileGrantStore.normalize's
|
|
11
|
+
// legacy-field handling. `withMigratedIdentity` backfills `identity.did` from
|
|
12
|
+
// `a2a.did` so the next `put` persists it forward (migrate-on-write), matching the
|
|
13
|
+
// resolver's local-id migration + the grant subjectFriendId→subjectKey pattern.
|
|
14
|
+
const observability_1 = require("./observability");
|
|
15
|
+
/** Read an agent's durable identity, preferring `meta.identity` and lifting the
|
|
16
|
+
* legacy `meta.a2a.did` on a miss (migrate-on-read). Returns `{}` for a did-less
|
|
17
|
+
* or absent meta. (Unit 4a stub — not implemented.) */
|
|
18
|
+
function resolveAgentIdentity(meta) {
|
|
19
|
+
if (!meta)
|
|
20
|
+
return {};
|
|
21
|
+
// Durable home wins (authoritative). Spread only the present optional fields so
|
|
22
|
+
// a partial identity ({ did } only) doesn't surface undefined keys. SECURITY
|
|
23
|
+
// (finding 6, LOW): an empty-string did is NOT a did — omit it so it can never be a
|
|
24
|
+
// matchable identity key (ties to findFriendByDid's falsy-did guard, finding 4).
|
|
25
|
+
//
|
|
26
|
+
// NOTE (finding 5-D): when BOTH meta.identity.did and a legacy meta.a2a.did are
|
|
27
|
+
// present and DIVERGE, the durable home silently wins here. That divergence can be a
|
|
28
|
+
// tampering signal (a record's legacy hint disagreeing with its pinned identity).
|
|
29
|
+
// We don't warn from this hot path (resolveAgentIdentity runs per-record in the
|
|
30
|
+
// findFriendByDid scan and per-audit-derivation); a divergence audit belongs in a
|
|
31
|
+
// write-time reconciliation (e.g. withMigratedIdentity / the onboard path), not here.
|
|
32
|
+
if (meta.identity) {
|
|
33
|
+
const { did, pinnedKey, handle, pinnedAt } = meta.identity;
|
|
34
|
+
return {
|
|
35
|
+
...(did ? { did } : {}),
|
|
36
|
+
...(pinnedKey !== undefined ? { pinnedKey } : {}),
|
|
37
|
+
...(handle !== undefined ? { handle } : {}),
|
|
38
|
+
...(pinnedAt !== undefined ? { pinnedAt } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Migrate-on-read: lift the legacy a2a.did hint when the durable home is absent.
|
|
42
|
+
// A falsy (absent or empty-string) hint is treated as no-did.
|
|
43
|
+
if (meta.a2a?.did)
|
|
44
|
+
return { did: meta.a2a.did };
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
/** Return a meta whose `identity.did` is backfilled from `a2a.did` when the durable
|
|
48
|
+
* home is absent (migrate-on-write); a meta already carrying `identity` is returned
|
|
49
|
+
* unchanged (no clobber). Absent meta is returned as-is. (Unit 4a stub.) */
|
|
50
|
+
function withMigratedIdentity(meta) {
|
|
51
|
+
if (!meta)
|
|
52
|
+
return undefined;
|
|
53
|
+
// Already carries the durable home → no clobber.
|
|
54
|
+
if (meta.identity)
|
|
55
|
+
return meta;
|
|
56
|
+
// Nothing to migrate from → unchanged. A falsy (absent or empty-string) a2a.did is
|
|
57
|
+
// not a real did to backfill (finding 6).
|
|
58
|
+
if (!meta.a2a?.did)
|
|
59
|
+
return meta;
|
|
60
|
+
// Backfill identity.did from the legacy a2a.did so the next put persists forward.
|
|
61
|
+
(0, observability_1.emitNervesEvent)({
|
|
62
|
+
component: "friends",
|
|
63
|
+
event: "friends.identity_migrated",
|
|
64
|
+
message: "backfilled AgentMeta.identity.did from legacy a2a.did",
|
|
65
|
+
meta: { did: meta.a2a.did },
|
|
66
|
+
});
|
|
67
|
+
return { ...meta, identity: { did: meta.a2a.did } };
|
|
68
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,8 +29,25 @@ export { upsertGroupContextParticipants } from "./group-context";
|
|
|
29
29
|
export { accumulateFriendTokens } from "./tokens";
|
|
30
30
|
export { applyFriendNote } from "./notes";
|
|
31
31
|
export { setFriendTrust } from "./trust-mutation";
|
|
32
|
+
export type { SetFriendTrustContext } from "./trust-mutation";
|
|
33
|
+
export { MemoryAuditSink, FileAuditSink, auditPathFor } from "./audit";
|
|
34
|
+
export type { AuditSink, ControlPlaneAuditRecord } from "./audit";
|
|
32
35
|
export { linkExternalId, unlinkExternalId } from "./link-identity";
|
|
33
36
|
export { upsertAgentPeer } from "./agent-peer";
|
|
37
|
+
export { connectAgents } from "./connect";
|
|
38
|
+
export type { ConnectPeer, ConnectAgentsInput, ConnectAgentsDeps, ConnectResult, ConnectStatus } from "./connect";
|
|
39
|
+
export { authorizeConnect } from "./connect-authority";
|
|
40
|
+
export type { AuthorizeConnectInput, ConnectAuthorization } from "./connect-authority";
|
|
41
|
+
export { resolveAgentIdentity, withMigratedIdentity } from "./identity";
|
|
42
|
+
export type { ResolvedAgentIdentity } from "./identity";
|
|
43
|
+
export { findFriendByDid } from "./friend-lookup";
|
|
44
|
+
export { FileRosterStore, rostersDirFor } from "./roster-store-file";
|
|
45
|
+
export type { RosterStore, AccountRoster, RosterPin } from "./roster-store";
|
|
46
|
+
export { identityRosterVerifier, DEFAULT_ROSTER_VERIFIER } from "./roster-verifier";
|
|
47
|
+
export type { RosterVerifier } from "./roster-verifier";
|
|
48
|
+
export { MemoryRosterStore } from "./roster-store-memory";
|
|
49
|
+
export { evaluateAccountMembership, verifiedCandidate, _resetRosterVerifierWarningForTest } from "./account-roster";
|
|
50
|
+
export type { AccountMembershipDecision, AccountMembershipResult, EvaluateAccountMembershipInput, VerifiedCandidate, } from "./account-roster";
|
|
34
51
|
export { recordRelationshipOutcome } from "./outcomes";
|
|
35
52
|
export { recordMission } from "./missions";
|
|
36
53
|
export type { RecordMissionInput } from "./missions";
|
|
@@ -44,6 +61,9 @@ export { prepareMissionShare, importMissionShare } from "./mission-share";
|
|
|
44
61
|
export type { MissionShareEnvelope, SharedLearning, PrepareMissionShareInput, PrepareMissionShareResult, PrepareMissionShareStatus, ImportMissionShareInput, ImportMissionShareOptions, ImportMissionShareResult, ImportMissionShareStatus, } from "./mission-share";
|
|
45
62
|
export { prepareCoordination, importCoordination } from "./coordination";
|
|
46
63
|
export type { CoordinationEnvelope, PrepareCoordinationInput, PrepareCoordinationResult, PrepareCoordinationStatus, ImportCoordinationInput, ImportCoordinationOptions, ImportCoordinationResult, ImportCoordinationStatus, } from "./coordination";
|
|
64
|
+
export { prepareMissionResult, importMissionResult } from "./mission-result";
|
|
65
|
+
export type { PrepareMissionResultInput, PrepareMissionResultResult, PrepareMissionResultStatus, ImportMissionResultInput, ImportMissionResultOptions, ImportMissionResultResult, ImportMissionResultStatus, } from "./mission-result";
|
|
66
|
+
export type { MissionTaskSpec, MissionResult, MissionResultEnvelope } from "./types";
|
|
47
67
|
export { grantShare, revokeShare, listShares, isGrantEffective } from "./grants";
|
|
48
68
|
export { emitNervesEvent, setNervesEmitter, } from "./observability";
|
|
49
69
|
export type { NervesEvent, NervesEmitter, LogLevel } from "./observability";
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// multi-agent (a2a peer) aware, consumed through the FriendStore interface +
|
|
7
7
|
// FriendResolver.
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
10
|
-
exports.setNervesEmitter = exports.emitNervesEvent = exports.isGrantEffective = exports.listShares = void 0;
|
|
9
|
+
exports.recordMission = exports.recordRelationshipOutcome = exports._resetRosterVerifierWarningForTest = exports.verifiedCandidate = exports.evaluateAccountMembership = exports.MemoryRosterStore = exports.DEFAULT_ROSTER_VERIFIER = exports.identityRosterVerifier = exports.rostersDirFor = exports.FileRosterStore = exports.findFriendByDid = exports.withMigratedIdentity = exports.resolveAgentIdentity = exports.authorizeConnect = exports.connectAgents = exports.upsertAgentPeer = exports.unlinkExternalId = exports.linkExternalId = exports.auditPathFor = exports.FileAuditSink = exports.MemoryAuditSink = exports.setFriendTrust = exports.applyFriendNote = exports.accumulateFriendTokens = exports.upsertGroupContextParticipants = exports.DEFAULT_STANDING_RULE = exports.explainStanding = exports.assessStanding = exports.describeTrustContext = exports.getAlwaysOnSenseNames = exports.isRemoteChannel = exports.channelToFacing = exports.getChannelCapabilities = exports._setMachineOwnerUsernameForTest = exports.isLocalMachineOwnerIdentity = exports.machineOwnerUsername = exports.FriendResolver = exports.openFileBundle = exports.missionsDirFor = exports.FileMissionStore = exports.grantsDirFor = exports.FileGrantStore = exports.FileFriendStore = exports.isCoordinationIntent = exports.isShareScope = exports.isIntegration = exports.isIdentityProvider = exports.isTrustedLevel = exports.IDENTITY_SCOPES = exports.TRUSTED_LEVELS = void 0;
|
|
10
|
+
exports.setNervesEmitter = exports.emitNervesEvent = exports.isGrantEffective = exports.listShares = exports.revokeShare = exports.grantShare = exports.importMissionResult = exports.prepareMissionResult = exports.importCoordination = exports.prepareCoordination = exports.importMissionShare = exports.prepareMissionShare = exports.importProfileShare = exports.prepareProfileShare = exports.DEFAULT_AGENT_VERIFIER = exports.tofuVerifier = exports.DEFAULT_CONSENT_POLICY = exports.tieredPolicy = exports.trustImpliedPolicy = exports.strictPolicy = exports.resolveRoom = exports.whoami = void 0;
|
|
11
11
|
// -- Values --
|
|
12
12
|
var types_1 = require("./types");
|
|
13
13
|
Object.defineProperty(exports, "TRUSTED_LEVELS", { enumerable: true, get: function () { return types_1.TRUSTED_LEVELS; } });
|
|
@@ -52,11 +52,50 @@ var notes_1 = require("./notes");
|
|
|
52
52
|
Object.defineProperty(exports, "applyFriendNote", { enumerable: true, get: function () { return notes_1.applyFriendNote; } });
|
|
53
53
|
var trust_mutation_1 = require("./trust-mutation");
|
|
54
54
|
Object.defineProperty(exports, "setFriendTrust", { enumerable: true, get: function () { return trust_mutation_1.setFriendTrust; } });
|
|
55
|
+
// -- Control-plane audit (Bug B): append-only record of trust mutations --
|
|
56
|
+
var audit_1 = require("./audit");
|
|
57
|
+
Object.defineProperty(exports, "MemoryAuditSink", { enumerable: true, get: function () { return audit_1.MemoryAuditSink; } });
|
|
58
|
+
Object.defineProperty(exports, "FileAuditSink", { enumerable: true, get: function () { return audit_1.FileAuditSink; } });
|
|
59
|
+
Object.defineProperty(exports, "auditPathFor", { enumerable: true, get: function () { return audit_1.auditPathFor; } });
|
|
55
60
|
var link_identity_1 = require("./link-identity");
|
|
56
61
|
Object.defineProperty(exports, "linkExternalId", { enumerable: true, get: function () { return link_identity_1.linkExternalId; } });
|
|
57
62
|
Object.defineProperty(exports, "unlinkExternalId", { enumerable: true, get: function () { return link_identity_1.unlinkExternalId; } });
|
|
58
63
|
var agent_peer_1 = require("./agent-peer");
|
|
59
64
|
Object.defineProperty(exports, "upsertAgentPeer", { enumerable: true, get: function () { return agent_peer_1.upsertAgentPeer; } });
|
|
65
|
+
// -- connect_to (brick 8, p11 inc2): the owner links one of their OWN agents into the
|
|
66
|
+
// fleet, gated to a management sense (local/closed) — `local` commits; an `open` sense
|
|
67
|
+
// downgrades to a confirm-prompt; `closed` is gated by a roster/membership check (never a
|
|
68
|
+
// blanket allow); a bare name with no resolvable handle/DID returns needs_handle (never
|
|
69
|
+
// fabricated). Writes one `action:"connect"` control-plane audit. `authorizeConnect` is the
|
|
70
|
+
// pure authority predicate (consumes a pre-computed AccountMembershipResult — core-clean).
|
|
71
|
+
var connect_1 = require("./connect");
|
|
72
|
+
Object.defineProperty(exports, "connectAgents", { enumerable: true, get: function () { return connect_1.connectAgents; } });
|
|
73
|
+
var connect_authority_1 = require("./connect-authority");
|
|
74
|
+
Object.defineProperty(exports, "authorizeConnect", { enumerable: true, get: function () { return connect_authority_1.authorizeConnect; } });
|
|
75
|
+
// -- Agent identity (p11 Item 2 — DID re-key): durable home + migrate-on-read --
|
|
76
|
+
var identity_1 = require("./identity");
|
|
77
|
+
Object.defineProperty(exports, "resolveAgentIdentity", { enumerable: true, get: function () { return identity_1.resolveAgentIdentity; } });
|
|
78
|
+
Object.defineProperty(exports, "withMigratedIdentity", { enumerable: true, get: function () { return identity_1.withMigratedIdentity; } });
|
|
79
|
+
// did-aware friend lookup (the durable cross-agent primary key is the DID).
|
|
80
|
+
var friend_lookup_1 = require("./friend-lookup");
|
|
81
|
+
Object.defineProperty(exports, "findFriendByDid", { enumerable: true, get: function () { return friend_lookup_1.findFriendByDid; } });
|
|
82
|
+
// -- Account roster (p11 Item 3): pinned roster + TOFU roster-key storage seam --
|
|
83
|
+
var roster_store_file_1 = require("./roster-store-file");
|
|
84
|
+
Object.defineProperty(exports, "FileRosterStore", { enumerable: true, get: function () { return roster_store_file_1.FileRosterStore; } });
|
|
85
|
+
Object.defineProperty(exports, "rostersDirFor", { enumerable: true, get: function () { return roster_store_file_1.rostersDirFor; } });
|
|
86
|
+
// -- RosterVerifier seam (Q1): core declares the interface + identity-only default;
|
|
87
|
+
// the a2a-client provides the Ed25519 impl (host-injected). Core stays crypto-free.
|
|
88
|
+
var roster_verifier_1 = require("./roster-verifier");
|
|
89
|
+
Object.defineProperty(exports, "identityRosterVerifier", { enumerable: true, get: function () { return roster_verifier_1.identityRosterVerifier; } });
|
|
90
|
+
Object.defineProperty(exports, "DEFAULT_ROSTER_VERIFIER", { enumerable: true, get: function () { return roster_verifier_1.DEFAULT_ROSTER_VERIFIER; } });
|
|
91
|
+
var roster_store_memory_1 = require("./roster-store-memory");
|
|
92
|
+
Object.defineProperty(exports, "MemoryRosterStore", { enumerable: true, get: function () { return roster_store_memory_1.MemoryRosterStore; } });
|
|
93
|
+
// -- Account-roster membership (Item 3 payoff): family via same_account for a
|
|
94
|
+
// key-verified, TOFU-pinned roster member; changed roster key hard-fails. --
|
|
95
|
+
var account_roster_1 = require("./account-roster");
|
|
96
|
+
Object.defineProperty(exports, "evaluateAccountMembership", { enumerable: true, get: function () { return account_roster_1.evaluateAccountMembership; } });
|
|
97
|
+
Object.defineProperty(exports, "verifiedCandidate", { enumerable: true, get: function () { return account_roster_1.verifiedCandidate; } });
|
|
98
|
+
Object.defineProperty(exports, "_resetRosterVerifierWarningForTest", { enumerable: true, get: function () { return account_roster_1._resetRosterVerifierWarningForTest; } });
|
|
60
99
|
var outcomes_1 = require("./outcomes");
|
|
61
100
|
Object.defineProperty(exports, "recordRelationshipOutcome", { enumerable: true, get: function () { return outcomes_1.recordRelationshipOutcome; } });
|
|
62
101
|
var missions_1 = require("./missions");
|
|
@@ -91,6 +130,16 @@ Object.defineProperty(exports, "importMissionShare", { enumerable: true, get: fu
|
|
|
91
130
|
var coordination_1 = require("./coordination");
|
|
92
131
|
Object.defineProperty(exports, "prepareCoordination", { enumerable: true, get: function () { return coordination_1.prepareCoordination; } });
|
|
93
132
|
Object.defineProperty(exports, "importCoordination", { enumerable: true, get: function () { return coordination_1.importCoordination; } });
|
|
133
|
+
// -- Result-return / delegation deliverable (gap-2, p11 inc2): B returns its result --
|
|
134
|
+
// prepareMissionResult (producer) / importMissionResult (consumer) carry B's actual
|
|
135
|
+
// produced deliverable back to A — attributed to B, correlated to A's delegation via
|
|
136
|
+
// missionKey + requestId, consent-gated via the existing "coordinate" scope, lands
|
|
137
|
+
// quarantined + attributed on import, trust-capped, non-transitive, first-party-inviolable.
|
|
138
|
+
// `MissionTaskSpec` (gap-1) rides the CoordinationEnvelope; the new MissionRecord fields
|
|
139
|
+
// (delegations / importedDelegations / results / importedResults) are additive.
|
|
140
|
+
var mission_result_1 = require("./mission-result");
|
|
141
|
+
Object.defineProperty(exports, "prepareMissionResult", { enumerable: true, get: function () { return mission_result_1.prepareMissionResult; } });
|
|
142
|
+
Object.defineProperty(exports, "importMissionResult", { enumerable: true, get: function () { return mission_result_1.importMissionResult; } });
|
|
94
143
|
var grants_1 = require("./grants");
|
|
95
144
|
Object.defineProperty(exports, "grantShare", { enumerable: true, get: function () { return grants_1.grantShare; } });
|
|
96
145
|
Object.defineProperty(exports, "revokeShare", { enumerable: true, get: function () { return grants_1.revokeShare; } });
|
package/dist/mailbox/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ProfileShareEnvelope } from "../share";
|
|
2
2
|
import type { MissionShareEnvelope } from "../mission-share";
|
|
3
3
|
import type { CoordinationEnvelope } from "../coordination";
|
|
4
|
+
import type { MissionResultEnvelope } from "../types";
|
|
4
5
|
/** The mailbox wire-format version. Bumped only on a breaking message change. */
|
|
5
6
|
export declare const MAILBOX_VERSION = 1;
|
|
6
7
|
/** A mailbox message: the TRANSPORT wrapper around a verbatim share envelope. The
|
|
@@ -14,19 +15,21 @@ export interface MailboxMessage {
|
|
|
14
15
|
toAgentId: string;
|
|
15
16
|
issuedAt: string;
|
|
16
17
|
/** The payload discriminant. The host branches on it to call importProfileShare
|
|
17
|
-
* vs importMissionShare vs importCoordination. The mailbox
|
|
18
|
-
* payload-agnostic — this union grows by one leaf per brick (additive,
|
|
19
|
-
* compatible); buildOutgoing/readIncoming carry any of them unchanged.
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
* vs importMissionShare vs importCoordination vs importMissionResult. The mailbox
|
|
19
|
+
* itself is payload-agnostic — this union grows by one leaf per brick (additive,
|
|
20
|
+
* backward-compatible); buildOutgoing/readIncoming carry any of them unchanged.
|
|
21
|
+
* `mission_result` (gap-2) carries B's delegation deliverable back to A. */
|
|
22
|
+
kind: "profile_share" | "mission_share" | "coordination" | "mission_result";
|
|
23
|
+
envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope | MissionResultEnvelope;
|
|
22
24
|
}
|
|
23
25
|
export interface BuildOutgoingInput {
|
|
24
|
-
envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope;
|
|
26
|
+
envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope | MissionResultEnvelope;
|
|
25
27
|
fromAgentId: string;
|
|
26
28
|
toAgentId: string;
|
|
27
29
|
/** The payload discriminant. Defaults to "profile_share" for backward-compat;
|
|
28
|
-
* a mission share passes "mission_share", a coordination message "coordination"
|
|
29
|
-
|
|
30
|
+
* a mission share passes "mission_share", a coordination message "coordination",
|
|
31
|
+
* a result-return "mission_result" (gap-2). */
|
|
32
|
+
kind?: "profile_share" | "mission_share" | "coordination" | "mission_result";
|
|
30
33
|
/** Injectable ISO clock for deterministic tests; defaults to now. */
|
|
31
34
|
now?: string;
|
|
32
35
|
}
|
|
@@ -54,8 +57,8 @@ export interface IncomingMessage {
|
|
|
54
57
|
fromAgentId: string;
|
|
55
58
|
toAgentId: string;
|
|
56
59
|
issuedAt: string;
|
|
57
|
-
kind: "profile_share" | "mission_share" | "coordination";
|
|
58
|
-
envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope;
|
|
60
|
+
kind: "profile_share" | "mission_share" | "coordination" | "mission_result";
|
|
61
|
+
envelope: ProfileShareEnvelope | MissionShareEnvelope | CoordinationEnvelope | MissionResultEnvelope;
|
|
59
62
|
relativePath: string;
|
|
60
63
|
}
|
|
61
64
|
export interface ReadIncomingInput {
|
package/dist/mailbox/index.js
CHANGED
|
@@ -86,7 +86,7 @@ function isWellFormedWrapper(value) {
|
|
|
86
86
|
typeof value.fromAgentId === "string" &&
|
|
87
87
|
typeof value.toAgentId === "string" &&
|
|
88
88
|
typeof value.issuedAt === "string" &&
|
|
89
|
-
(value.kind === "profile_share" || value.kind === "mission_share" || value.kind === "coordination") &&
|
|
89
|
+
(value.kind === "profile_share" || value.kind === "mission_share" || value.kind === "coordination" || value.kind === "mission_result") &&
|
|
90
90
|
typeof value.envelope === "object" &&
|
|
91
91
|
value.envelope !== null &&
|
|
92
92
|
!Array.isArray(value.envelope));
|
package/dist/mcp/dispatch.d.ts
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
import type { FriendStore } from "../store";
|
|
2
2
|
import type { GrantStore } from "../grant-store";
|
|
3
3
|
import type { MissionStore } from "../mission-store";
|
|
4
|
+
import type { AuditSink } from "../audit";
|
|
5
|
+
import type { SenseType } from "../types";
|
|
6
|
+
import type { AccountMembershipResult } from "../account-roster";
|
|
4
7
|
type Args = Record<string, unknown>;
|
|
5
8
|
export interface DispatchResult {
|
|
6
9
|
result: unknown;
|
|
7
10
|
isError: boolean;
|
|
8
11
|
}
|
|
12
|
+
/** WHO/WHENCE context stamped onto a control-plane audit record (finding 3). The MCP
|
|
13
|
+
* server passes the local owner/sense it was constructed with. */
|
|
14
|
+
export interface ControlPlaneContext {
|
|
15
|
+
actor?: string;
|
|
16
|
+
originSense?: string;
|
|
17
|
+
/** The management SENSE the gate evaluates for `connect_to` (p11 inc2, brick 8).
|
|
18
|
+
* The stdio path is owner-only, so this defaults to `local` (`?? "local"`) — a
|
|
19
|
+
* `local` management sense COMMITS. A network/multi-tenant transport that constructs
|
|
20
|
+
* the server MUST pass its real senseType (`open` ⇒ confirm-prompt downgrade; `closed`
|
|
21
|
+
* ⇒ gated by `membership`). Distinct from `originSense` (a free-form audit string like
|
|
22
|
+
* "stdio"); this is the typed SenseType the authority predicate consumes. */
|
|
23
|
+
senseType?: SenseType;
|
|
24
|
+
/** The PRE-COMPUTED account-roster membership for a `closed`-sense `connect_to`
|
|
25
|
+
* (p11 inc2). The stdio `local` path never consults it (left `undefined`); a `closed`
|
|
26
|
+
* network transport supplies the membership it already evaluated against the roster.
|
|
27
|
+
* The boundary stays thin — it forwards this to the library, computing no membership
|
|
28
|
+
* itself (the MCP `resolve_party` path does not wire a roster context). */
|
|
29
|
+
membership?: AccountMembershipResult;
|
|
30
|
+
}
|
|
31
|
+
/** Whether wiring an AuditSink should also stamp a record for an `onboard_agent` trust
|
|
32
|
+
* seat: only when the owner explicitly set a trustLevel (a deliberate trust decision).
|
|
33
|
+
* A cold contact with no trustLevel lands at the safe `stranger` default (Bug A) and is
|
|
34
|
+
* NOT an owner trust mutation, so it is not audited. */
|
|
9
35
|
export declare function coerceBool(v: unknown): boolean;
|
|
10
36
|
export declare function coerceInt(v: unknown): number | undefined;
|
|
11
37
|
export declare function coerceString(v: unknown): string;
|
|
12
38
|
export declare function coerceOptionalString(v: unknown): string | undefined;
|
|
13
|
-
export declare function dispatchTool(store: FriendStore, name: string, args: Args, grants?: GrantStore, missions?: MissionStore): Promise<DispatchResult>;
|
|
39
|
+
export declare function dispatchTool(store: FriendStore, name: string, args: Args, grants?: GrantStore, missions?: MissionStore, audit?: AuditSink, controlContext?: ControlPlaneContext): Promise<DispatchResult>;
|
|
14
40
|
export {};
|
package/dist/mcp/dispatch.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.dispatchTool = dispatchTool;
|
|
|
11
11
|
// the library fns. `dispatchTool` is a flat tool → library-fn map (D9/D10) with
|
|
12
12
|
// NO domain logic of its own — every behavior lives in the friends library.
|
|
13
13
|
const observability_1 = require("../observability");
|
|
14
|
+
const identity_1 = require("../identity");
|
|
14
15
|
const types_1 = require("../types");
|
|
15
16
|
const resolver_1 = require("../resolver");
|
|
16
17
|
const trust_explanation_1 = require("../trust-explanation");
|
|
@@ -31,6 +32,19 @@ const missions_1 = require("../missions");
|
|
|
31
32
|
const mission_share_1 = require("../mission-share");
|
|
32
33
|
const coordination_1 = require("../coordination");
|
|
33
34
|
const types_2 = require("../types");
|
|
35
|
+
const connect_1 = require("../connect");
|
|
36
|
+
const mission_result_1 = require("../mission-result");
|
|
37
|
+
/** SECURITY (finding 3-A): the friends MCP server speaks JSON-RPC over **stdio**, and
|
|
38
|
+
* stdio is an owner-only channel — the local user who launched the process is the only
|
|
39
|
+
* actor. So when no explicit controlContext is wired, audited mutations are attributed
|
|
40
|
+
* to the stdio owner boundary rather than the generic "unknown". A network/multi-tenant
|
|
41
|
+
* transport MUST pass its own authenticated actor instead of relying on these. */
|
|
42
|
+
const STDIO_OWNER_ACTOR = "owner:stdio";
|
|
43
|
+
const STDIO_ORIGIN_SENSE = "stdio";
|
|
44
|
+
/** Whether wiring an AuditSink should also stamp a record for an `onboard_agent` trust
|
|
45
|
+
* seat: only when the owner explicitly set a trustLevel (a deliberate trust decision).
|
|
46
|
+
* A cold contact with no trustLevel lands at the safe `stranger` default (Bug A) and is
|
|
47
|
+
* NOT an owner trust mutation, so it is not audited. */
|
|
34
48
|
function coerceBool(v) {
|
|
35
49
|
return v === true || v === "true";
|
|
36
50
|
}
|
|
@@ -69,13 +83,18 @@ const NO_GRANT_STORE = { ok: false, status: "unsupported", message: "no grant st
|
|
|
69
83
|
* embedding). The mission ledger needs mission persistence, so report it cleanly
|
|
70
84
|
* rather than guessing. */
|
|
71
85
|
const NO_MISSION_STORE = { ok: false, status: "unsupported", message: "no mission store configured (mission tools require one)" };
|
|
72
|
-
async function dispatchTool(store, name, args, grants, missions) {
|
|
86
|
+
async function dispatchTool(store, name, args, grants, missions, audit, controlContext) {
|
|
73
87
|
(0, observability_1.emitNervesEvent)({
|
|
74
88
|
component: "clients",
|
|
75
89
|
event: "clients.mcp_dispatch",
|
|
76
90
|
message: "dispatching friends mcp tool",
|
|
77
91
|
meta: { tool: name },
|
|
78
92
|
});
|
|
93
|
+
// SECURITY (finding 3 / 3-A): resolve the WHO/WHENCE for an audited mutation. With
|
|
94
|
+
// no explicit context, attribute to the stdio owner boundary (the only actor on an
|
|
95
|
+
// owner-only stdio channel) rather than the generic "unknown".
|
|
96
|
+
const auditActor = controlContext?.actor ?? STDIO_OWNER_ACTOR;
|
|
97
|
+
const auditOriginSense = controlContext?.originSense ?? STDIO_ORIGIN_SENSE;
|
|
79
98
|
switch (name) {
|
|
80
99
|
case "resolve_party": {
|
|
81
100
|
const provider = coerceString(args.provider);
|
|
@@ -188,7 +207,14 @@ async function dispatchTool(store, name, args, grants, missions) {
|
|
|
188
207
|
return { result: results, isError: false };
|
|
189
208
|
}
|
|
190
209
|
case "set_trust": {
|
|
191
|
-
|
|
210
|
+
// SECURITY (finding 3): thread the audit sink + owner/sense context so the LIVE
|
|
211
|
+
// trust mutation actually writes a control-plane record. With no sink wired,
|
|
212
|
+
// setFriendTrust treats the ctx as a no-op (back-compat).
|
|
213
|
+
const result = await (0, trust_mutation_1.setFriendTrust)(store, coerceString(args.friendId), coerceString(args.trustLevel), {
|
|
214
|
+
...(audit ? { sink: audit } : {}),
|
|
215
|
+
actor: auditActor,
|
|
216
|
+
originSense: auditOriginSense,
|
|
217
|
+
});
|
|
192
218
|
return { result, isError: result.ok === false };
|
|
193
219
|
}
|
|
194
220
|
case "link_identity": {
|
|
@@ -207,16 +233,59 @@ async function dispatchTool(store, name, args, grants, missions) {
|
|
|
207
233
|
return { result, isError: result.ok === false };
|
|
208
234
|
}
|
|
209
235
|
case "onboard_agent": {
|
|
236
|
+
const explicitTrustLevel = coerceOptionalString(args.trustLevel);
|
|
210
237
|
const record = await (0, agent_peer_1.upsertAgentPeer)(store, {
|
|
211
238
|
name: coerceString(args.name),
|
|
212
239
|
agentId: coerceString(args.agentId),
|
|
213
|
-
trustLevel:
|
|
240
|
+
trustLevel: explicitTrustLevel,
|
|
214
241
|
a2a: parseMaybeJson(args.a2a),
|
|
215
242
|
mailbox: parseMaybeJson(args.mailbox),
|
|
216
243
|
bundleName: coerceOptionalString(args.bundleName),
|
|
217
244
|
});
|
|
245
|
+
// SECURITY (finding 3): an owner-initiated trust SEAT (an explicit trustLevel) is
|
|
246
|
+
// a control-plane trust mutation, so audit it through the wired sink. A cold
|
|
247
|
+
// contact with no trustLevel falls to the safe `stranger` default (Bug A) — not
|
|
248
|
+
// an owner trust decision — so it is left unaudited.
|
|
249
|
+
if (audit && explicitTrustLevel !== undefined) {
|
|
250
|
+
const targetDid = (0, identity_1.resolveAgentIdentity)(record.agentMeta).did;
|
|
251
|
+
const auditRecord = {
|
|
252
|
+
action: "set_trust",
|
|
253
|
+
targetId: record.id,
|
|
254
|
+
...(targetDid !== undefined ? { targetDid } : {}),
|
|
255
|
+
level: explicitTrustLevel,
|
|
256
|
+
actor: auditActor,
|
|
257
|
+
originSense: auditOriginSense,
|
|
258
|
+
ts: record.updatedAt,
|
|
259
|
+
};
|
|
260
|
+
await audit.append(auditRecord);
|
|
261
|
+
}
|
|
218
262
|
return { result: record, isError: false };
|
|
219
263
|
}
|
|
264
|
+
case "connect_to": {
|
|
265
|
+
// The management-sense control plane (p11 inc2, brick 8). The boundary stays
|
|
266
|
+
// thin — coerce the peer handles + level, resolve the gate's management sense
|
|
267
|
+
// (the stdio path is owner-only ⇒ `local`), and forward to the library, which
|
|
268
|
+
// owns the authority gate + disambiguation + introduce + audit. `isError` reflects
|
|
269
|
+
// ok===false (a `downgraded` / `needs_handle_or_introduction` result is an error
|
|
270
|
+
// result like the other mutation cases).
|
|
271
|
+
const result = await (0, connect_1.connectAgents)(store, {
|
|
272
|
+
peer: {
|
|
273
|
+
agentId: coerceOptionalString(args.agentId),
|
|
274
|
+
did: coerceOptionalString(args.did),
|
|
275
|
+
name: coerceOptionalString(args.name),
|
|
276
|
+
},
|
|
277
|
+
// The stdio default is `local` (owner-only); a network transport supplies its
|
|
278
|
+
// real senseType via controlContext. The proof's stdio path commits.
|
|
279
|
+
senseType: controlContext?.senseType ?? "local",
|
|
280
|
+
...(controlContext?.membership ? { membership: controlContext.membership } : {}),
|
|
281
|
+
trustLevel: coerceOptionalString(args.trustLevel),
|
|
282
|
+
}, {
|
|
283
|
+
...(audit ? { audit } : {}),
|
|
284
|
+
actor: auditActor,
|
|
285
|
+
originSense: auditOriginSense,
|
|
286
|
+
});
|
|
287
|
+
return { result, isError: result.ok === false };
|
|
288
|
+
}
|
|
220
289
|
case "whoami": {
|
|
221
290
|
return { result: await (0, whoami_1.whoami)(store), isError: false };
|
|
222
291
|
}
|
|
@@ -396,6 +465,9 @@ async function dispatchTool(store, name, args, grants, missions) {
|
|
|
396
465
|
note: coerceOptionalString(args.note),
|
|
397
466
|
proposedAssignee: parseMaybeJson(args.proposedAssignee),
|
|
398
467
|
selfAgentId,
|
|
468
|
+
// gap-1 (p11 inc2): an optional task-spec, meaningful only on a `request`. The
|
|
469
|
+
// library mints the requestId + records the delegation first-party.
|
|
470
|
+
task: parseMaybeJson(args.task),
|
|
399
471
|
proof: coerceOptionalString(args.proof),
|
|
400
472
|
});
|
|
401
473
|
return { result, isError: result.ok === false };
|
|
@@ -425,6 +497,41 @@ async function dispatchTool(store, name, args, grants, missions) {
|
|
|
425
497
|
}
|
|
426
498
|
return { result: record.coordination ?? { assignee: undefined, log: [] }, isError: false };
|
|
427
499
|
}
|
|
500
|
+
case "send_result": {
|
|
501
|
+
// Producer (gap-2): B returns its deliverable. Self identity comes from whoami
|
|
502
|
+
// (the dispatch is store-only); the mission is named by its missionKey inside the
|
|
503
|
+
// library. Gated on BOTH a GrantStore (consent via the "coordinate" scope) and a
|
|
504
|
+
// MissionStore — exactly like share_mission.
|
|
505
|
+
if (!missions || !grants)
|
|
506
|
+
return { result: NO_MISSION_STORE, isError: true };
|
|
507
|
+
const self = await (0, whoami_1.whoami)(store);
|
|
508
|
+
const selfAgentId = self.selfFriendId ?? "";
|
|
509
|
+
const result = await (0, mission_result_1.prepareMissionResult)(missions, store, grants, {
|
|
510
|
+
missionId: coerceString(args.missionId),
|
|
511
|
+
toAgentId: coerceString(args.toAgentId),
|
|
512
|
+
requestId: coerceString(args.requestId),
|
|
513
|
+
result: parseMaybeJson(args.result) ?? { summary: "" },
|
|
514
|
+
selfAgentId,
|
|
515
|
+
proof: coerceOptionalString(args.proof),
|
|
516
|
+
});
|
|
517
|
+
return { result, isError: result.ok === false };
|
|
518
|
+
}
|
|
519
|
+
case "import_result": {
|
|
520
|
+
// Consumer (gap-2): A imports B's deliverable. Gated on the MissionStore, like
|
|
521
|
+
// import_mission. An invalid/malformed envelope is a clean `invalid` result.
|
|
522
|
+
if (!missions)
|
|
523
|
+
return { result: NO_MISSION_STORE, isError: true };
|
|
524
|
+
const envelope = parseMaybeJson(args.envelope);
|
|
525
|
+
if (!envelope || typeof envelope !== "object") {
|
|
526
|
+
return { result: { ok: false, status: "invalid", message: "an envelope object is required" }, isError: true };
|
|
527
|
+
}
|
|
528
|
+
const result = await (0, mission_result_1.importMissionResult)(missions, {
|
|
529
|
+
envelope,
|
|
530
|
+
fromAgentId: coerceString(args.fromAgentId),
|
|
531
|
+
trustOfSource: coerceString(args.trustOfSource),
|
|
532
|
+
});
|
|
533
|
+
return { result, isError: result.ok === false };
|
|
534
|
+
}
|
|
428
535
|
default: {
|
|
429
536
|
return { result: { error: `Unknown tool: ${name}` }, isError: true };
|
|
430
537
|
}
|
package/dist/mcp/run-main.js
CHANGED
|
@@ -35,11 +35,14 @@ function runMain(argv, env, io) {
|
|
|
35
35
|
message: "friends mcp run-main",
|
|
36
36
|
meta: { source },
|
|
37
37
|
});
|
|
38
|
-
// The consent-grant + mission collections are sibling `_grants
|
|
39
|
-
// dirs under the friends dir, so the single `--dir` wires the
|
|
40
|
-
//
|
|
41
|
-
const { store, grants, missions } = (0, file_bundle_1.openFileBundle)(dir);
|
|
42
|
-
|
|
38
|
+
// The consent-grant + mission + audit collections are sibling `_grants/`,
|
|
39
|
+
// `_missions/`, `_audit/` dirs under the friends dir, so the single `--dir` wires the
|
|
40
|
+
// whole substrate. `openFileBundle` encapsulates that convention.
|
|
41
|
+
const { store, grants, missions, audit } = (0, file_bundle_1.openFileBundle)(dir);
|
|
42
|
+
// SECURITY (finding 3 / 3-A): thread the FileAuditSink into the live server so trust
|
|
43
|
+
// mutations are audited. The `friends-mcp` bin speaks owner-only stdio, so the
|
|
44
|
+
// default actor/originSense (the stdio owner boundary) is the correct attribution.
|
|
45
|
+
const server = (0, server_1.createFriendsMcpServer)({ store, grants, missions, audit, stdin: io.stdin, stdout: io.stdout });
|
|
43
46
|
server.start();
|
|
44
47
|
return server;
|
|
45
48
|
}
|
package/dist/mcp/schemas.js
CHANGED
|
@@ -3,14 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getToolSchemas = getToolSchemas;
|
|
4
4
|
// MCP tool schemas for the friends server.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
6
|
+
// 32 tools — a thin 1:1 surface over the friends library (D7): the original 14,
|
|
7
7
|
// the cross-agent moat surface (resolve_room, import_profile, grant_share,
|
|
8
8
|
// revoke_share, list_shares; share_profile is de-stubbed in place), the brick-3
|
|
9
9
|
// mission ledger (record_mission, get_mission, list_missions, share_mission,
|
|
10
10
|
// import_mission), the brick-4 earned-standing lenses (assess_standing,
|
|
11
11
|
// explain_standing — read-only, advisory; never write trust, never on the wire),
|
|
12
|
-
//
|
|
13
|
-
// get_coordination — negotiate WHO does a mission; advisory assignment metadata)
|
|
12
|
+
// the brick-5 coordination verbs (coordinate, import_coordination,
|
|
13
|
+
// get_coordination — negotiate WHO does a mission; advisory assignment metadata),
|
|
14
|
+
// and the p11-inc2 own-fleet delegation surface (connect_to — the management-sense
|
|
15
|
+
// control plane that links two own agents + audits action:"connect"; send_result,
|
|
16
|
+
// import_result — the result-return / delegation deliverable channel that carries
|
|
17
|
+
// B's produced artifact back to A, attributed + correlated + quarantined on import).
|
|
14
18
|
// Each schema follows JSON Schema for
|
|
15
19
|
// `inputSchema` as required by MCP. The shape mirrors the harness's McpToolSchema
|
|
16
20
|
// so the same client tooling consumes both.
|
|
@@ -187,7 +191,7 @@ function getToolSchemas() {
|
|
|
187
191
|
properties: {
|
|
188
192
|
name: { type: "string", description: "the peer agent's name" },
|
|
189
193
|
agentId: { type: "string", description: "the a2a agent id" },
|
|
190
|
-
trustLevel: { type: "string", description: "trust level (default
|
|
194
|
+
trustLevel: { type: "string", description: "trust level (default stranger (cold contact))" },
|
|
191
195
|
a2a: { type: "object", description: "a2a coordinates { cardUrl?, endpointUrl?, protocolVersion? }" },
|
|
192
196
|
mailbox: { type: "object", description: "optional A2A git-mailbox coords { repo, selfOutboxAgentId }" },
|
|
193
197
|
bundleName: { type: "string", description: "optional bundle name" },
|
|
@@ -195,6 +199,20 @@ function getToolSchemas() {
|
|
|
195
199
|
required: ["name", "agentId"],
|
|
196
200
|
},
|
|
197
201
|
},
|
|
202
|
+
{
|
|
203
|
+
name: "connect_to",
|
|
204
|
+
description: "Management-sense control plane (brick 8): the owner links one of their OWN agents into this agent's fleet — introduce a peer by agentId/did/name at a trust level (default family for own-fleet). Authority-gated: commits inline ONLY from a local (owner-only stdio) or roster-verified same-account closed sense; an open sense never commits inline (it downgrades to a confirm-prompt); a bare name with no resolvable handle/DID and no record hit returns needs_handle_or_introduction (never fabricates a target). Writes one control-plane audit record (action:'connect') through the wired sink. Returns { ok:true, status:'connected', record } or { ok:false, status:'downgraded'|'needs_handle_or_introduction', downgrade? }.",
|
|
205
|
+
inputSchema: {
|
|
206
|
+
type: "object",
|
|
207
|
+
properties: {
|
|
208
|
+
agentId: { type: "string", description: "the peer agent's join-key agentId (an owner-supplied handle)" },
|
|
209
|
+
did: { type: "string", description: "the peer's DID (an alternative handle; must resolve to an existing record)" },
|
|
210
|
+
name: { type: "string", description: "the peer's colloquial name (resolves ONLY by matching an existing record — never fabricated)" },
|
|
211
|
+
trustLevel: { type: "string", enum: ["family", "friend", "acquaintance", "stranger"], description: "the trust to link at (default family for own-fleet linked agents)" },
|
|
212
|
+
proof: { type: "string", description: "optional opaque proof slot (reserved; the TOFU path ignores it)" },
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
198
216
|
{
|
|
199
217
|
name: "whoami",
|
|
200
218
|
description: "Resolve who the machine owner is and which friend record represents the self.",
|
|
@@ -365,6 +383,7 @@ function getToolSchemas() {
|
|
|
365
383
|
intent: { type: "string", enum: ["request", "offer", "accept", "decline", "handoff"], description: "the coordination verb: request (will you take this?) / offer (I'll take this) / accept (yes, I'm on it — sets assignee=self) / decline (no) / handoff (it's yours now — you must hold the assignment; proposes a new assignee)" },
|
|
366
384
|
note: { type: "string", description: "optional free text carried on the message + logged" },
|
|
367
385
|
proposedAssignee: { type: "object", description: "the proposed new assignee { agentId?, agentName? } — meaningful ONLY on intent=handoff" },
|
|
386
|
+
task: { type: "object", description: "optional delegation task-spec { summary, details?, inputs? } — meaningful ONLY on intent=request (gap-2); the producer mints a requestId, stamps it on the envelope, and records the delegation first-party for the result-return to correlate against" },
|
|
368
387
|
proof: { type: "string", description: "optional opaque proof to stamp on the envelope (for a non-TOFU recipient verifier)" },
|
|
369
388
|
},
|
|
370
389
|
required: ["missionId", "toAgentId", "intent"],
|
|
@@ -394,5 +413,33 @@ function getToolSchemas() {
|
|
|
394
413
|
required: ["missionId"],
|
|
395
414
|
},
|
|
396
415
|
},
|
|
416
|
+
{
|
|
417
|
+
name: "send_result",
|
|
418
|
+
description: "Producer (gap-2 — the result-return): B returns its DELIVERABLE for a delegation, attributed to B (from whoami) + correlated to A's task-spec by requestId, named by the mission's missionKey (never the local uuid). Consent-gated via the identity-tier 'coordinate' scope (a result is B answering A's own delegation — trust ≥ friend suffices; NO new scope). Records the result first-party on B's own mission. Returns { ok, envelope } or { ok:false, status: not_found|no_consent }.",
|
|
419
|
+
inputSchema: {
|
|
420
|
+
type: "object",
|
|
421
|
+
properties: {
|
|
422
|
+
missionId: { type: "string", description: "the local mission B is returning a result for (its local uuid id)" },
|
|
423
|
+
toAgentId: { type: "string", description: "the recipient agent's join-key agentId — A, the delegator" },
|
|
424
|
+
requestId: { type: "string", description: "the delegation correlation key (the task-spec's requestId)" },
|
|
425
|
+
result: { type: "object", description: "B's deliverable { summary, artifact?, outputs? }" },
|
|
426
|
+
proof: { type: "string", description: "optional opaque proof to stamp on the envelope (for a non-TOFU recipient verifier)" },
|
|
427
|
+
},
|
|
428
|
+
required: ["missionId", "toAgentId", "requestId", "result"],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "import_result",
|
|
433
|
+
description: "Consumer (gap-2, non-clobbering merge): A imports B's result-return. Resolves the mission by missionKey; lands B's deliverable QUARANTINED + attributed under importedResults WITHOUT touching first-party; source trust caps acceptance (checked before correlation); a result whose requestId matches no prior first-party delegation is REJECTED (no_delegation — A only accepts results for work it delegated); an unknown mission is no_mission (a result never seeds a mission); never recomputes status/participants. Returns { ok, status, record } or { ok:false, status: untrusted_source|no_mission|no_delegation|invalid }.",
|
|
434
|
+
inputSchema: {
|
|
435
|
+
type: "object",
|
|
436
|
+
properties: {
|
|
437
|
+
envelope: { type: "object", description: "the MissionResultEnvelope to import" },
|
|
438
|
+
fromAgentId: { type: "string", description: "the agent the envelope arrived from (join-key agentId) — B" },
|
|
439
|
+
trustOfSource: { type: "string", enum: ["family", "friend", "acquaintance", "stranger"], description: "this agent's resolved trust in the source agent — the acceptance cap" },
|
|
440
|
+
},
|
|
441
|
+
required: ["envelope", "fromAgentId", "trustOfSource"],
|
|
442
|
+
},
|
|
443
|
+
},
|
|
397
444
|
];
|
|
398
445
|
}
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { FriendStore } from "../store";
|
|
2
2
|
import type { GrantStore } from "../grant-store";
|
|
3
3
|
import type { MissionStore } from "../mission-store";
|
|
4
|
+
import type { AuditSink } from "../audit";
|
|
5
|
+
import type { ControlPlaneContext } from "./dispatch";
|
|
4
6
|
export interface FriendsMcpServerOptions {
|
|
5
7
|
store: FriendStore;
|
|
6
8
|
/** Optional consent-grant store. When omitted, the consent/share tools
|
|
@@ -11,6 +13,13 @@ export interface FriendsMcpServerOptions {
|
|
|
11
13
|
* (record_mission / get_mission / list_missions / share_mission /
|
|
12
14
|
* import_mission) report `unsupported`; everything else works without it. */
|
|
13
15
|
missions?: MissionStore;
|
|
16
|
+
/** Optional control-plane audit sink (Bug B, finding 3). When wired, the LIVE
|
|
17
|
+
* trust mutations (`set_trust`, and an explicit-trust-seat `onboard_agent`) append
|
|
18
|
+
* an append-only audit record. When omitted, those mutations are unaudited. */
|
|
19
|
+
audit?: AuditSink;
|
|
20
|
+
/** Optional WHO/WHENCE context for audited mutations. Defaults to the stdio
|
|
21
|
+
* owner-only boundary (finding 3-A) when omitted. */
|
|
22
|
+
controlContext?: ControlPlaneContext;
|
|
14
23
|
stdin: NodeJS.ReadableStream;
|
|
15
24
|
stdout: NodeJS.WritableStream;
|
|
16
25
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -13,7 +13,7 @@ const observability_1 = require("../observability");
|
|
|
13
13
|
const schemas_1 = require("./schemas");
|
|
14
14
|
const dispatch_1 = require("./dispatch");
|
|
15
15
|
function createFriendsMcpServer(options) {
|
|
16
|
-
const { store, grants, missions, stdin, stdout } = options;
|
|
16
|
+
const { store, grants, missions, audit, controlContext, stdin, stdout } = options;
|
|
17
17
|
let buffer = "";
|
|
18
18
|
let running = false;
|
|
19
19
|
let useContentLengthFraming = true;
|
|
@@ -145,7 +145,7 @@ function createFriendsMcpServer(options) {
|
|
|
145
145
|
const toolName = params.name ?? "";
|
|
146
146
|
const toolArgs = params.arguments ?? {};
|
|
147
147
|
try {
|
|
148
|
-
const { result, isError } = await (0, dispatch_1.dispatchTool)(store, toolName, toolArgs, grants, missions);
|
|
148
|
+
const { result, isError } = await (0, dispatch_1.dispatchTool)(store, toolName, toolArgs, grants, missions, audit, controlContext);
|
|
149
149
|
writeResponse({
|
|
150
150
|
jsonrpc: "2.0",
|
|
151
151
|
id: request.id,
|