@ouro.bot/friends 0.1.0-alpha.4 → 0.1.0-alpha.6
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 +79 -8
- package/changelog.json +12 -0
- package/dist/a2a-client/a2a-message.d.ts +39 -0
- package/dist/a2a-client/a2a-message.js +54 -0
- package/dist/a2a-client/adapter.d.ts +97 -0
- package/dist/a2a-client/adapter.js +114 -0
- package/dist/a2a-client/agent-card.d.ts +50 -0
- package/dist/a2a-client/agent-card.js +32 -0
- package/dist/a2a-client/did-key.d.ts +38 -0
- package/dist/a2a-client/did-key.js +120 -0
- package/dist/a2a-client/did-verifier.d.ts +109 -0
- package/dist/a2a-client/did-verifier.js +163 -0
- package/dist/a2a-client/did-web.d.ts +26 -0
- package/dist/a2a-client/did-web.js +140 -0
- package/dist/a2a-client/index.d.ts +24 -0
- package/dist/a2a-client/index.js +76 -0
- package/dist/a2a-client/jcs.d.ts +5 -0
- package/dist/a2a-client/jcs.js +84 -0
- package/dist/a2a-client/reachability.d.ts +22 -0
- package/dist/a2a-client/reachability.js +17 -0
- package/dist/a2a-client/roster-verify.d.ts +15 -0
- package/dist/a2a-client/roster-verify.js +61 -0
- package/dist/a2a-client/seal.d.ts +47 -0
- package/dist/a2a-client/seal.js +95 -0
- package/dist/a2a-client/sealed-envelope.d.ts +55 -0
- package/dist/a2a-client/sealed-envelope.js +94 -0
- package/dist/a2a-client/sign.d.ts +42 -0
- package/dist/a2a-client/sign.js +87 -0
- package/dist/a2a-client/sodium.d.ts +5 -0
- package/dist/a2a-client/sodium.js +19 -0
- package/dist/account-roster.d.ts +52 -0
- package/dist/account-roster.js +108 -0
- package/dist/agent-peer.js +10 -2
- package/dist/audit.d.ts +38 -0
- package/dist/audit.js +86 -0
- 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 +13 -0
- package/dist/index.js +31 -2
- package/dist/{a2a → mailbox}/index.js +10 -3
- package/dist/mcp/bin.js +0 -0
- package/dist/mcp/dispatch.d.ts +12 -1
- package/dist/mcp/dispatch.js +45 -3
- package/dist/mcp/run-main.js +8 -5
- package/dist/mcp/schemas.js +1 -1
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.js +2 -2
- 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/store-file.d.ts +6 -2
- package/dist/store-file.js +28 -5
- 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 +33 -7
- package/package.json +15 -6
- /package/dist/{a2a → mailbox}/index.d.ts +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AccountRoster, RosterStore } from "./roster-store";
|
|
2
|
+
import type { RosterVerifier } from "./roster-verifier";
|
|
3
|
+
export type AccountMembershipDecision = "family_same_account" | "not_member" | "unverified" | "roster_key_mismatch";
|
|
4
|
+
/** SECURITY (finding 2, HIGH): an opaque "the caller has verified this peer controls
|
|
5
|
+
* this did" token. `evaluateAccountMembership` grants family ONLY for a value of this
|
|
6
|
+
* type, and the ONLY way to produce one is `verifiedCandidate(did)` — so the
|
|
7
|
+
* candidate-DID precondition is impossible to forget at a call site (a bare string
|
|
8
|
+
* does not type-check). The private brand makes it unforgeable from a plain object. */
|
|
9
|
+
export interface VerifiedCandidate {
|
|
10
|
+
readonly did: string;
|
|
11
|
+
/** Private brand — prevents `{ did }` from structurally satisfying the type. */
|
|
12
|
+
readonly [VERIFIED_BRAND]: true;
|
|
13
|
+
}
|
|
14
|
+
declare const VERIFIED_BRAND: unique symbol;
|
|
15
|
+
/** Mint a {@link VerifiedCandidate}. CALLING THIS IS AN ASSERTION: the caller has
|
|
16
|
+
* already proven (via a DID/pinned-key handshake — e.g. the a2a-client sealed-envelope
|
|
17
|
+
* gate that runs `DidVerifier` before this) that the peer controls `did`. Never call
|
|
18
|
+
* it on an attacker-supplied did that has not been authenticated. */
|
|
19
|
+
export declare function verifiedCandidate(did: string): VerifiedCandidate;
|
|
20
|
+
export interface EvaluateAccountMembershipInput {
|
|
21
|
+
roster: AccountRoster;
|
|
22
|
+
/** SECURITY (finding 2): the verified candidate — only mintable via
|
|
23
|
+
* `verifiedCandidate(did)` after the caller has authenticated the peer's control of
|
|
24
|
+
* the did. The roster membership + sig checks are NOT a proof of did-control on
|
|
25
|
+
* their own; this token supplies that missing precondition. */
|
|
26
|
+
candidate: VerifiedCandidate;
|
|
27
|
+
rosterKey: string;
|
|
28
|
+
store: RosterStore;
|
|
29
|
+
verifier?: RosterVerifier;
|
|
30
|
+
}
|
|
31
|
+
export interface AccountMembershipResult {
|
|
32
|
+
decision: AccountMembershipDecision;
|
|
33
|
+
reason?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Test seam: reset the one-time-warning latch so a test can assert the loud warning
|
|
36
|
+
* fires (and de-dupes) deterministically, independent of test order. */
|
|
37
|
+
export declare function _resetRosterVerifierWarningForTest(): void;
|
|
38
|
+
/** Decide whether the VERIFIED `candidate` is family-via-same-account under `roster`.
|
|
39
|
+
*
|
|
40
|
+
* Preconditions (all enforced, not merely documented):
|
|
41
|
+
* - The caller has authenticated that the peer controls `candidate.did` (carried by
|
|
42
|
+
* the unforgeable {@link VerifiedCandidate} — finding 2). Membership + sig are NOT
|
|
43
|
+
* a substitute for did-control.
|
|
44
|
+
* - A real cryptographic `verifier` (`grantsFamily: true`) is injected. The
|
|
45
|
+
* identity-only default fails closed: it can verify identity for non-grant checks
|
|
46
|
+
* but can NEVER produce a `family_same_account` grant (finding 1).
|
|
47
|
+
*
|
|
48
|
+
* Flow: TOFU-pin the roster key on first contact; a changed key hard-fails; the
|
|
49
|
+
* verifier must accept the roster; the verifier must be family-granting; the
|
|
50
|
+
* candidate's did must be in the roster. Any miss yields a non-family decision. */
|
|
51
|
+
export declare function evaluateAccountMembership(input: EvaluateAccountMembershipInput): Promise<AccountMembershipResult>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifiedCandidate = verifiedCandidate;
|
|
4
|
+
exports._resetRosterVerifierWarningForTest = _resetRosterVerifierWarningForTest;
|
|
5
|
+
exports.evaluateAccountMembership = evaluateAccountMembership;
|
|
6
|
+
// evaluateAccountMembership — the Increment-1 payoff (Item 3). Grants `family` via
|
|
7
|
+
// TrustBasis "same_account" ONLY to a peer whose `did` is in the pinned roster AND
|
|
8
|
+
// whose roster verifies under the TOFU-pinned roster key. A changed roster key
|
|
9
|
+
// HARD-FAILS (no silent re-pin).
|
|
10
|
+
//
|
|
11
|
+
// CORE module: it uses the INJECTED RosterVerifier + RosterStore seams and does NO
|
|
12
|
+
// direct crypto (no a2a-client / libsodium import) — the Ed25519 verifier is
|
|
13
|
+
// injected by the host/test. The lint enforces the dependency direction.
|
|
14
|
+
const observability_1 = require("./observability");
|
|
15
|
+
const roster_verifier_1 = require("./roster-verifier");
|
|
16
|
+
/** Mint a {@link VerifiedCandidate}. CALLING THIS IS AN ASSERTION: the caller has
|
|
17
|
+
* already proven (via a DID/pinned-key handshake — e.g. the a2a-client sealed-envelope
|
|
18
|
+
* gate that runs `DidVerifier` before this) that the peer controls `did`. Never call
|
|
19
|
+
* it on an attacker-supplied did that has not been authenticated. */
|
|
20
|
+
function verifiedCandidate(did) {
|
|
21
|
+
return { did };
|
|
22
|
+
}
|
|
23
|
+
/** One-time loud-warning latch: we warn at most once per process when a family grant
|
|
24
|
+
* is refused purely because the active verifier is not cryptographic (finding 1). */
|
|
25
|
+
let warnedNonCryptographicVerifier = false;
|
|
26
|
+
/** Test seam: reset the one-time-warning latch so a test can assert the loud warning
|
|
27
|
+
* fires (and de-dupes) deterministically, independent of test order. */
|
|
28
|
+
function _resetRosterVerifierWarningForTest() {
|
|
29
|
+
warnedNonCryptographicVerifier = false;
|
|
30
|
+
}
|
|
31
|
+
/** Decide whether the VERIFIED `candidate` is family-via-same-account under `roster`.
|
|
32
|
+
*
|
|
33
|
+
* Preconditions (all enforced, not merely documented):
|
|
34
|
+
* - The caller has authenticated that the peer controls `candidate.did` (carried by
|
|
35
|
+
* the unforgeable {@link VerifiedCandidate} — finding 2). Membership + sig are NOT
|
|
36
|
+
* a substitute for did-control.
|
|
37
|
+
* - A real cryptographic `verifier` (`grantsFamily: true`) is injected. The
|
|
38
|
+
* identity-only default fails closed: it can verify identity for non-grant checks
|
|
39
|
+
* but can NEVER produce a `family_same_account` grant (finding 1).
|
|
40
|
+
*
|
|
41
|
+
* Flow: TOFU-pin the roster key on first contact; a changed key hard-fails; the
|
|
42
|
+
* verifier must accept the roster; the verifier must be family-granting; the
|
|
43
|
+
* candidate's did must be in the roster. Any miss yields a non-family decision. */
|
|
44
|
+
async function evaluateAccountMembership(input) {
|
|
45
|
+
const { roster, candidate, rosterKey, store } = input;
|
|
46
|
+
const accountId = roster.accountId;
|
|
47
|
+
// 1) Roster-key pin (TOFU). First contact pins the key; an EXISTING pin for a
|
|
48
|
+
// DIFFERENT key HARD-FAILS (no silent re-pin); a matching pin proceeds.
|
|
49
|
+
const existingPin = await store.getPin(accountId);
|
|
50
|
+
if (!existingPin) {
|
|
51
|
+
await store.putPin({ accountId, rosterKey, pinnedAt: new Date().toISOString() });
|
|
52
|
+
}
|
|
53
|
+
else if (existingPin.rosterKey !== rosterKey) {
|
|
54
|
+
const result = {
|
|
55
|
+
decision: "roster_key_mismatch",
|
|
56
|
+
reason: "presented roster key does not match the pinned key",
|
|
57
|
+
};
|
|
58
|
+
emit(result.decision, accountId);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
// 2) Authenticity: the injected verifier (or the identity default) must accept
|
|
62
|
+
// the roster under the pinned/presented key.
|
|
63
|
+
const verifier = input.verifier ?? roster_verifier_1.DEFAULT_ROSTER_VERIFIER;
|
|
64
|
+
if (!verifier.verify(roster, rosterKey)) {
|
|
65
|
+
const result = { decision: "unverified", reason: "roster signature did not verify" };
|
|
66
|
+
emit(result.decision, accountId);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
// 2b) SECURITY (finding 1, HIGH): FAIL CLOSED on the family-granting path. The
|
|
70
|
+
// identity-only default accepts any well-formed roster (it ignores the sig), so it
|
|
71
|
+
// MUST NOT be allowed to grant family — only a real cryptographic verifier
|
|
72
|
+
// (`grantsFamily: true`) can. Without one, the strongest tier is unreachable: a
|
|
73
|
+
// would-be member is `unverified`, never `family_same_account`. Warn LOUDLY once.
|
|
74
|
+
if (verifier.grantsFamily !== true) {
|
|
75
|
+
if (!warnedNonCryptographicVerifier) {
|
|
76
|
+
warnedNonCryptographicVerifier = true;
|
|
77
|
+
(0, observability_1.emitNervesEvent)({
|
|
78
|
+
level: "warn",
|
|
79
|
+
component: "friends",
|
|
80
|
+
event: "friends.roster_verifier_not_cryptographic",
|
|
81
|
+
message: "REFUSING to grant family_same_account: no cryptographic RosterVerifier injected (the identity-only default cannot back a family grant). Inject ed25519RosterVerifier to enable same-account family.",
|
|
82
|
+
meta: { accountId },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const result = {
|
|
86
|
+
decision: "unverified",
|
|
87
|
+
reason: "no cryptographic roster verifier injected — family grant withheld (fail-closed)",
|
|
88
|
+
};
|
|
89
|
+
emit(result.decision, accountId);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
// 3) Membership: the candidate's did must be in the verified roster.
|
|
93
|
+
const isMember = roster.members.some((m) => m.did === candidate.did);
|
|
94
|
+
const result = isMember
|
|
95
|
+
? { decision: "family_same_account" }
|
|
96
|
+
: { decision: "not_member", reason: "candidate did is not in the roster" };
|
|
97
|
+
emit(result.decision, accountId);
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
/** Emit the membership-evaluated nerves event. */
|
|
101
|
+
function emit(decision, accountId) {
|
|
102
|
+
(0, observability_1.emitNervesEvent)({
|
|
103
|
+
component: "friends",
|
|
104
|
+
event: "friends.account_membership_evaluated",
|
|
105
|
+
message: "evaluated account-roster membership",
|
|
106
|
+
meta: { accountId, decision },
|
|
107
|
+
});
|
|
108
|
+
}
|
package/dist/agent-peer.js
CHANGED
|
@@ -13,7 +13,11 @@ async function upsertAgentPeer(store, input) {
|
|
|
13
13
|
const { name, agentId, a2a, bundleName } = input;
|
|
14
14
|
const existing = await store.findByExternalId("a2a-agent", agentId);
|
|
15
15
|
const now = new Date().toISOString();
|
|
16
|
-
|
|
16
|
+
// Bug A — cold contact is safe-by-default: a brand-new peer with no explicit
|
|
17
|
+
// trustLevel and no existing record lands at `stranger`, not `acquaintance`. An
|
|
18
|
+
// owner-initiated onboard that passes an explicit `trustLevel`, and an existing
|
|
19
|
+
// record's level, both still win (they precede this fallback).
|
|
20
|
+
const trustLevel = input.trustLevel ?? existing?.trustLevel ?? "stranger";
|
|
17
21
|
const baseMeta = existing?.agentMeta ?? {
|
|
18
22
|
bundleName: bundleName ?? name,
|
|
19
23
|
familiarity: 0,
|
|
@@ -36,9 +40,13 @@ async function upsertAgentPeer(store, input) {
|
|
|
36
40
|
trustLevel,
|
|
37
41
|
kind: "agent",
|
|
38
42
|
agentMeta: {
|
|
43
|
+
// `...baseMeta` already carries any existing top-level `mailbox`; an explicit
|
|
44
|
+
// `input.mailbox` overrides it below. Mailbox is top-level on AgentMeta since
|
|
45
|
+
// the phase-8 demote (was nested under `a2a` in alpha.4).
|
|
39
46
|
...baseMeta,
|
|
40
47
|
bundleName: baseMeta.bundleName || bundleName || name,
|
|
41
|
-
a2a: { ...(a2a ?? {}), agentId
|
|
48
|
+
a2a: { ...(a2a ?? {}), agentId },
|
|
49
|
+
...(input.mailbox ? { mailbox: input.mailbox } : {}),
|
|
42
50
|
},
|
|
43
51
|
externalIds: [
|
|
44
52
|
...(existing?.externalIds.filter((id) => !(id.provider === "a2a-agent" && id.externalId === agentId)) ?? []),
|
package/dist/audit.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TrustBasis } from "./trust-explanation";
|
|
2
|
+
import type { TrustLevel } from "./types";
|
|
3
|
+
/** One append-only control-plane audit record. Captures a single trust mutation:
|
|
4
|
+
* WHO (`actor`), to WHOM (`targetId` / optional `targetDid`), the new `level`, the
|
|
5
|
+
* `basis` it was granted on, the `originSense` it came through, and WHEN (`ts`). */
|
|
6
|
+
export interface ControlPlaneAuditRecord {
|
|
7
|
+
action: "set_trust";
|
|
8
|
+
targetId: string;
|
|
9
|
+
targetDid?: string;
|
|
10
|
+
level: TrustLevel;
|
|
11
|
+
basis?: TrustBasis;
|
|
12
|
+
actor: string;
|
|
13
|
+
originSense?: string;
|
|
14
|
+
ts: string;
|
|
15
|
+
}
|
|
16
|
+
/** The append-only sink a control-plane mutation writes through. The host
|
|
17
|
+
* implements it (in-memory in tests, a file/JSONL adapter in production). */
|
|
18
|
+
export interface AuditSink {
|
|
19
|
+
append(record: ControlPlaneAuditRecord): Promise<void> | void;
|
|
20
|
+
}
|
|
21
|
+
/** In-memory append-only sink — test/host convenience, mirroring MemoryPinStore.
|
|
22
|
+
* `list()` exposes the records in append order; there is no overwrite. */
|
|
23
|
+
export declare class MemoryAuditSink implements AuditSink {
|
|
24
|
+
private readonly records;
|
|
25
|
+
append(record: ControlPlaneAuditRecord): void;
|
|
26
|
+
list(): ControlPlaneAuditRecord[];
|
|
27
|
+
}
|
|
28
|
+
/** The append-only control-plane log file for a given friends directory:
|
|
29
|
+
* `<friendsDir>/_audit/control.jsonl`. A reserved `_`-prefixed sibling (like
|
|
30
|
+
* `_grants/`) so one `--dir` covers it; JSONL so appends never rewrite history. */
|
|
31
|
+
export declare function auditPathFor(friendsDir: string): string;
|
|
32
|
+
/** Filesystem AuditSink — appends each record as one JSON line to
|
|
33
|
+
* `_audit/control.jsonl`. mkdir-on-construct, mirroring FileGrantStore. */
|
|
34
|
+
export declare class FileAuditSink implements AuditSink {
|
|
35
|
+
private readonly filePath;
|
|
36
|
+
constructor(filePath: string);
|
|
37
|
+
append(record: ControlPlaneAuditRecord): Promise<void>;
|
|
38
|
+
}
|
package/dist/audit.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FileAuditSink = exports.MemoryAuditSink = void 0;
|
|
37
|
+
exports.auditPathFor = auditPathFor;
|
|
38
|
+
// Control-plane audit (Bug B) — an append-only record of every trust mutation.
|
|
39
|
+
//
|
|
40
|
+
// The control plane is "who changed a peer's standing, from where, and why". The
|
|
41
|
+
// package must stay storage-agnostic (and 100%-coverable), so the audit is an
|
|
42
|
+
// injectable SINK — not a hard-wired `fs` write — mirroring the observability
|
|
43
|
+
// seam and the GrantStore/FileGrantStore split. `setFriendTrust` writes one record
|
|
44
|
+
// on a successful mutation; the host wires a `FileAuditSink` (or its own) to
|
|
45
|
+
// persist it. With no sink injected, the mutation is unchanged (no-op audit).
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const fsPromises = __importStar(require("fs/promises"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const observability_1 = require("./observability");
|
|
50
|
+
/** In-memory append-only sink — test/host convenience, mirroring MemoryPinStore.
|
|
51
|
+
* `list()` exposes the records in append order; there is no overwrite. */
|
|
52
|
+
class MemoryAuditSink {
|
|
53
|
+
records = [];
|
|
54
|
+
append(record) {
|
|
55
|
+
this.records.push(record);
|
|
56
|
+
}
|
|
57
|
+
list() {
|
|
58
|
+
return [...this.records];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.MemoryAuditSink = MemoryAuditSink;
|
|
62
|
+
/** The append-only control-plane log file for a given friends directory:
|
|
63
|
+
* `<friendsDir>/_audit/control.jsonl`. A reserved `_`-prefixed sibling (like
|
|
64
|
+
* `_grants/`) so one `--dir` covers it; JSONL so appends never rewrite history. */
|
|
65
|
+
function auditPathFor(friendsDir) {
|
|
66
|
+
return path.join(friendsDir, "_audit", "control.jsonl");
|
|
67
|
+
}
|
|
68
|
+
/** Filesystem AuditSink — appends each record as one JSON line to
|
|
69
|
+
* `_audit/control.jsonl`. mkdir-on-construct, mirroring FileGrantStore. */
|
|
70
|
+
class FileAuditSink {
|
|
71
|
+
filePath;
|
|
72
|
+
constructor(filePath) {
|
|
73
|
+
this.filePath = filePath;
|
|
74
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
75
|
+
(0, observability_1.emitNervesEvent)({
|
|
76
|
+
component: "friends",
|
|
77
|
+
event: "friends.audit_sink_init",
|
|
78
|
+
message: "file audit sink initialized",
|
|
79
|
+
meta: {},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async append(record) {
|
|
83
|
+
await fsPromises.appendFile(this.filePath, JSON.stringify(record) + "\n", "utf-8");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.FileAuditSink = FileAuditSink;
|
package/dist/file-bundle.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { FileFriendStore } from "./store-file";
|
|
2
2
|
import { FileGrantStore } from "./grant-store-file";
|
|
3
3
|
import { FileMissionStore } from "./mission-store-file";
|
|
4
|
+
import { FileAuditSink } from "./audit";
|
|
4
5
|
export interface FileBundle {
|
|
5
6
|
store: FileFriendStore;
|
|
6
7
|
grants: FileGrantStore;
|
|
7
8
|
missions: FileMissionStore;
|
|
9
|
+
/** Control-plane audit sink (Bug B, finding 3) over the sibling `_audit/control.jsonl`,
|
|
10
|
+
* so the live MCP `set_trust` / `onboard_agent` trust seat write audit records. */
|
|
11
|
+
audit: FileAuditSink;
|
|
8
12
|
friendsDir: string;
|
|
9
13
|
grantsDir: string;
|
|
10
14
|
missionsDir: string;
|
|
15
|
+
auditPath: string;
|
|
11
16
|
}
|
|
12
17
|
export declare function openFileBundle(friendsDir: string): FileBundle;
|
package/dist/file-bundle.js
CHANGED
|
@@ -7,17 +7,21 @@ exports.openFileBundle = openFileBundle;
|
|
|
7
7
|
const store_file_1 = require("./store-file");
|
|
8
8
|
const grant_store_file_1 = require("./grant-store-file");
|
|
9
9
|
const mission_store_file_1 = require("./mission-store-file");
|
|
10
|
+
const audit_1 = require("./audit");
|
|
10
11
|
const observability_1 = require("./observability");
|
|
11
12
|
function openFileBundle(friendsDir) {
|
|
12
13
|
const grantsDir = (0, grant_store_file_1.grantsDirFor)(friendsDir);
|
|
13
14
|
const missionsDir = (0, mission_store_file_1.missionsDirFor)(friendsDir);
|
|
15
|
+
const auditPath = (0, audit_1.auditPathFor)(friendsDir);
|
|
14
16
|
(0, observability_1.emitNervesEvent)({ component: "friends", event: "friends.file_bundle_opened", message: "opened file bundle", meta: {} });
|
|
15
17
|
return {
|
|
16
18
|
store: new store_file_1.FileFriendStore(friendsDir),
|
|
17
19
|
grants: new grant_store_file_1.FileGrantStore(grantsDir),
|
|
18
20
|
missions: new mission_store_file_1.FileMissionStore(missionsDir),
|
|
21
|
+
audit: new audit_1.FileAuditSink(auditPath),
|
|
19
22
|
friendsDir,
|
|
20
23
|
grantsDir,
|
|
21
24
|
missionsDir,
|
|
25
|
+
auditPath,
|
|
22
26
|
};
|
|
23
27
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FriendStore } from "./store";
|
|
2
|
+
import type { FriendRecord } from "./types";
|
|
3
|
+
/** Find the friend record whose durable identity DID equals `did`. A DUPLICATE did is
|
|
4
|
+
* an anomaly: it emits a loud `friends.duplicate_did` warning and resolves
|
|
5
|
+
* deterministically WITHOUT rewarding back-dating — a pinned/verified record wins, else
|
|
6
|
+
* the lowest record `id` (a stable, non-temporal tie-break) — see {@link preferOverBest}.
|
|
7
|
+
* Returns null when no record matches, the query did is falsy, or the store has no
|
|
8
|
+
* `listAll`. */
|
|
9
|
+
export declare function findFriendByDid(store: FriendStore, did: string): Promise<FriendRecord | null>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findFriendByDid = findFriendByDid;
|
|
4
|
+
// did-aware friend lookup (p11 Item 2 — the DID re-key).
|
|
5
|
+
//
|
|
6
|
+
// `did` is the durable cross-agent primary key. This pure helper finds a friend
|
|
7
|
+
// record by did WITHOUT changing the FriendStore interface contract: it scans
|
|
8
|
+
// `store.listAll?.()` and matches on the record's resolved identity
|
|
9
|
+
// (`resolveAgentIdentity(f.agentMeta).did`, which already prefers identity.did and
|
|
10
|
+
// migrates a2a.did on read). Additive — `findByExternalId` is untouched. A store
|
|
11
|
+
// with no `listAll` yields null (the lookup is best-effort, never a throw).
|
|
12
|
+
const observability_1 = require("./observability");
|
|
13
|
+
const identity_1 = require("./identity");
|
|
14
|
+
/** Whether `candidate` should replace the current best among duplicate-did records.
|
|
15
|
+
*
|
|
16
|
+
* SECURITY (finding 5, MEDIUM): the tie-break must NOT reward back-dating — the old
|
|
17
|
+
* "lowest createdAt wins" rule let an attacker mint a duplicate-did record with an
|
|
18
|
+
* earlier createdAt to silently shadow a legit one. Instead:
|
|
19
|
+
* 1) Prefer a trust-relevant signal — a record carrying a TOFU-pinned key
|
|
20
|
+
* (`pinnedKey`) is the verified one and beats an unpinned duplicate.
|
|
21
|
+
* 2) When pinned-status is equal, break the tie by the record `id` (a stable,
|
|
22
|
+
* non-temporal key) — back-dating `createdAt` no longer gains anything. */
|
|
23
|
+
function preferOverBest(candidate, best) {
|
|
24
|
+
const candidatePinned = (0, identity_1.resolveAgentIdentity)(candidate.agentMeta).pinnedKey !== undefined;
|
|
25
|
+
const bestPinned = (0, identity_1.resolveAgentIdentity)(best.agentMeta).pinnedKey !== undefined;
|
|
26
|
+
if (candidatePinned !== bestPinned)
|
|
27
|
+
return candidatePinned; // pinned beats unpinned
|
|
28
|
+
return candidate.id < best.id; // stable, non-temporal tie-break
|
|
29
|
+
}
|
|
30
|
+
/** Find the friend record whose durable identity DID equals `did`. A DUPLICATE did is
|
|
31
|
+
* an anomaly: it emits a loud `friends.duplicate_did` warning and resolves
|
|
32
|
+
* deterministically WITHOUT rewarding back-dating — a pinned/verified record wins, else
|
|
33
|
+
* the lowest record `id` (a stable, non-temporal tie-break) — see {@link preferOverBest}.
|
|
34
|
+
* Returns null when no record matches, the query did is falsy, or the store has no
|
|
35
|
+
* `listAll`. */
|
|
36
|
+
async function findFriendByDid(store, did) {
|
|
37
|
+
// SECURITY (finding 4, MEDIUM): a falsy did query must never match. Without this,
|
|
38
|
+
// findFriendByDid(store, undefined|"") matched the first did-less record (a did-less
|
|
39
|
+
// record resolves to `undefined`, and `undefined !== undefined` is false → match).
|
|
40
|
+
if (!did)
|
|
41
|
+
return null;
|
|
42
|
+
if (typeof store.listAll !== "function")
|
|
43
|
+
return null;
|
|
44
|
+
const all = await store.listAll();
|
|
45
|
+
let best = null;
|
|
46
|
+
let matchCount = 0;
|
|
47
|
+
for (const f of all) {
|
|
48
|
+
const resolvedDid = (0, identity_1.resolveAgentIdentity)(f.agentMeta).did;
|
|
49
|
+
// Skip records whose resolved did is falsy (absent/empty) so they can never match —
|
|
50
|
+
// belt-and-braces with resolveAgentIdentity's own empty-string guard (finding 6).
|
|
51
|
+
if (!resolvedDid || resolvedDid !== did)
|
|
52
|
+
continue;
|
|
53
|
+
matchCount += 1;
|
|
54
|
+
if (best === null || preferOverBest(f, best))
|
|
55
|
+
best = f;
|
|
56
|
+
}
|
|
57
|
+
// SECURITY (finding 5): a duplicate did is itself an anomaly — surface it loudly so a
|
|
58
|
+
// shadowing attempt is visible, rather than silently resolving it away.
|
|
59
|
+
if (matchCount > 1) {
|
|
60
|
+
(0, observability_1.emitNervesEvent)({
|
|
61
|
+
level: "warn",
|
|
62
|
+
component: "friends",
|
|
63
|
+
event: "friends.duplicate_did",
|
|
64
|
+
message: `duplicate did detected across ${matchCount} friend records — resolving to the pinned/lowest-id record (NOT lowest-createdAt); investigate possible record shadowing`,
|
|
65
|
+
meta: { did, matchCount },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return best;
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentMeta } from "./types";
|
|
2
|
+
/** The resolved durable identity of an agent peer — independent of which on-disk
|
|
3
|
+
* shape carried it. All fields optional: a did-less legacy record reads clean. */
|
|
4
|
+
export interface ResolvedAgentIdentity {
|
|
5
|
+
did?: string;
|
|
6
|
+
pinnedKey?: string;
|
|
7
|
+
handle?: string;
|
|
8
|
+
pinnedAt?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Read an agent's durable identity, preferring `meta.identity` and lifting the
|
|
11
|
+
* legacy `meta.a2a.did` on a miss (migrate-on-read). Returns `{}` for a did-less
|
|
12
|
+
* or absent meta. (Unit 4a stub — not implemented.) */
|
|
13
|
+
export declare function resolveAgentIdentity(meta: AgentMeta | undefined): ResolvedAgentIdentity;
|
|
14
|
+
/** Return a meta whose `identity.did` is backfilled from `a2a.did` when the durable
|
|
15
|
+
* home is absent (migrate-on-write); a meta already carrying `identity` is returned
|
|
16
|
+
* unchanged (no clobber). Absent meta is returned as-is. (Unit 4a stub.) */
|
|
17
|
+
export declare function withMigratedIdentity(meta: AgentMeta | undefined): AgentMeta | undefined;
|
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,21 @@ 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 { resolveAgentIdentity, withMigratedIdentity } from "./identity";
|
|
38
|
+
export type { ResolvedAgentIdentity } from "./identity";
|
|
39
|
+
export { findFriendByDid } from "./friend-lookup";
|
|
40
|
+
export { FileRosterStore, rostersDirFor } from "./roster-store-file";
|
|
41
|
+
export type { RosterStore, AccountRoster, RosterPin } from "./roster-store";
|
|
42
|
+
export { identityRosterVerifier, DEFAULT_ROSTER_VERIFIER } from "./roster-verifier";
|
|
43
|
+
export type { RosterVerifier } from "./roster-verifier";
|
|
44
|
+
export { MemoryRosterStore } from "./roster-store-memory";
|
|
45
|
+
export { evaluateAccountMembership, verifiedCandidate, _resetRosterVerifierWarningForTest } from "./account-roster";
|
|
46
|
+
export type { AccountMembershipDecision, AccountMembershipResult, EvaluateAccountMembershipInput, VerifiedCandidate, } from "./account-roster";
|
|
34
47
|
export { recordRelationshipOutcome } from "./outcomes";
|
|
35
48
|
export { recordMission } from "./missions";
|
|
36
49
|
export type { RecordMissionInput } from "./missions";
|
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.resolveRoom = exports.whoami = 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.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.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 = 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,40 @@ 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
|
+
// -- Agent identity (p11 Item 2 — DID re-key): durable home + migrate-on-read --
|
|
66
|
+
var identity_1 = require("./identity");
|
|
67
|
+
Object.defineProperty(exports, "resolveAgentIdentity", { enumerable: true, get: function () { return identity_1.resolveAgentIdentity; } });
|
|
68
|
+
Object.defineProperty(exports, "withMigratedIdentity", { enumerable: true, get: function () { return identity_1.withMigratedIdentity; } });
|
|
69
|
+
// did-aware friend lookup (the durable cross-agent primary key is the DID).
|
|
70
|
+
var friend_lookup_1 = require("./friend-lookup");
|
|
71
|
+
Object.defineProperty(exports, "findFriendByDid", { enumerable: true, get: function () { return friend_lookup_1.findFriendByDid; } });
|
|
72
|
+
// -- Account roster (p11 Item 3): pinned roster + TOFU roster-key storage seam --
|
|
73
|
+
var roster_store_file_1 = require("./roster-store-file");
|
|
74
|
+
Object.defineProperty(exports, "FileRosterStore", { enumerable: true, get: function () { return roster_store_file_1.FileRosterStore; } });
|
|
75
|
+
Object.defineProperty(exports, "rostersDirFor", { enumerable: true, get: function () { return roster_store_file_1.rostersDirFor; } });
|
|
76
|
+
// -- RosterVerifier seam (Q1): core declares the interface + identity-only default;
|
|
77
|
+
// the a2a-client provides the Ed25519 impl (host-injected). Core stays crypto-free.
|
|
78
|
+
var roster_verifier_1 = require("./roster-verifier");
|
|
79
|
+
Object.defineProperty(exports, "identityRosterVerifier", { enumerable: true, get: function () { return roster_verifier_1.identityRosterVerifier; } });
|
|
80
|
+
Object.defineProperty(exports, "DEFAULT_ROSTER_VERIFIER", { enumerable: true, get: function () { return roster_verifier_1.DEFAULT_ROSTER_VERIFIER; } });
|
|
81
|
+
var roster_store_memory_1 = require("./roster-store-memory");
|
|
82
|
+
Object.defineProperty(exports, "MemoryRosterStore", { enumerable: true, get: function () { return roster_store_memory_1.MemoryRosterStore; } });
|
|
83
|
+
// -- Account-roster membership (Item 3 payoff): family via same_account for a
|
|
84
|
+
// key-verified, TOFU-pinned roster member; changed roster key hard-fails. --
|
|
85
|
+
var account_roster_1 = require("./account-roster");
|
|
86
|
+
Object.defineProperty(exports, "evaluateAccountMembership", { enumerable: true, get: function () { return account_roster_1.evaluateAccountMembership; } });
|
|
87
|
+
Object.defineProperty(exports, "verifiedCandidate", { enumerable: true, get: function () { return account_roster_1.verifiedCandidate; } });
|
|
88
|
+
Object.defineProperty(exports, "_resetRosterVerifierWarningForTest", { enumerable: true, get: function () { return account_roster_1._resetRosterVerifierWarningForTest; } });
|
|
60
89
|
var outcomes_1 = require("./outcomes");
|
|
61
90
|
Object.defineProperty(exports, "recordRelationshipOutcome", { enumerable: true, get: function () { return outcomes_1.recordRelationshipOutcome; } });
|
|
62
91
|
var missions_1 = require("./missions");
|
|
@@ -6,7 +6,13 @@ exports.compareReady = compareReady;
|
|
|
6
6
|
exports.readIncoming = readIncoming;
|
|
7
7
|
exports.isSeen = isSeen;
|
|
8
8
|
exports.markSeen = markSeen;
|
|
9
|
-
// src/
|
|
9
|
+
// src/mailbox — the pure git-mailbox format/routing/dedup library (the demoted
|
|
10
|
+
// offline/no-endpoint FALLBACK transport, NOT the primary A2A path).
|
|
11
|
+
//
|
|
12
|
+
// Real A2A (`message/send` + the friends E2E sign-then-seal overlay — see
|
|
13
|
+
// src/a2a-client/) is the PRIMARY cross-agent transport. This git-mailbox
|
|
14
|
+
// survives only as a clearly-labelled fallback for peers with no reachable
|
|
15
|
+
// endpoint and no relay; a host opts into it explicitly.
|
|
10
16
|
//
|
|
11
17
|
// A consumer agent and a producer agent that authenticate as two DISTINCT git
|
|
12
18
|
// identities share a dedicated PRIVATE mailbox repo. This module computes the
|
|
@@ -17,8 +23,9 @@ exports.markSeen = markSeen;
|
|
|
17
23
|
// • NO fs / net / http / child_process / process.env / git anywhere — the wire
|
|
18
24
|
// (clone / pull / add / commit / push) is entirely the caller's job.
|
|
19
25
|
// Type-only imports of `ProfileShareEnvelope` (../share) + `MissionShareEnvelope`
|
|
20
|
-
// (../mission-share) carry no runtime edge. Both are CORE modules, so the
|
|
21
|
-
// import direction is eslint-legal (
|
|
26
|
+
// (../mission-share) carry no runtime edge. Both are CORE modules, so the
|
|
27
|
+
// mailbox→core import direction is eslint-legal (mailbox may import core; the
|
|
28
|
+
// reverse is forbidden).
|
|
22
29
|
//
|
|
23
30
|
// Security model (the git-native TOFU): addressing lives in the PATH, and a
|
|
24
31
|
// single-writer-per-outbox-dir layout means a forged sender can't write into
|
package/dist/mcp/bin.js
CHANGED
|
File without changes
|