@inetafrica/open-claudia 3.3.1 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/channels/kazee/adapter.js +5 -0
- package/core/actions.js +16 -4
- package/core/enforcer.js +30 -2
- package/core/intro-flow.js +81 -16
- package/core/router.js +17 -7
- package/package.json +2 -2
- package/test-enforcer.js +31 -0
- package/test-mandate-hygiene.js +23 -5
- package/test-provider-language.js +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.3.2 — Known people are recognized by transport id, approved owners stay owners, and a mandate can no longer land on the wrong note
|
|
4
|
+
|
|
5
|
+
- **Every known person got interrogated on a fresh Kazee DM.** The intro flow matched inbound chats only by `(adapter, channelId)` — but Kazee DMs key the envelope's channelId by the conversation *document* id while people handles store the sender's *user* id, so nothing ever matched: known teammates, and the owner on pods without `KAZEE_OWNER_USER_ID`, all got "who are you" (the Coders Africa incident, 2026-08-19/20). Now a sender whose transport user id already maps to a person is silently **adopted**: the DM channel is linked to their record, an auth entry is written, any stale pending intro is resolved, and `handleInbound` returns false so the router falls through and handles the turn normally. Adoption is gated on adapter-attested `surface: "dm"` — the Kazee adapter sets it only on a positive chat-meta read (`isGroupChat: false`); a failed meta read attests nothing (fail closed) — because auth.json standing is per-CHANNEL and adopting a room would hand every member the sender's standing. Display names are never matched; only the transport id, the same trust level as the env owner match.
|
|
6
|
+
- **Approving an owner onto a second channel demoted them there.** `applyApproval` hardcoded `isOwner: false` into the auth entry while `isChatOwner` reads exactly that file. The entry now mirrors `person.isOwner`, and a stale `false` entry from the bug era is upgraded in place on the next approval.
|
|
7
|
+
- **The mandate writer can no longer be steered by a display name.** `appendMandateLine`'s legacy fallback upserted by `personName` when the record carried no slug — a spoofable transport display name could land owner-authored security text on a same-named OTHER person's note (reachable from telegram-group externals, where there is no person record). Writes are now strictly slug-keyed: `entitySlug`, else the person REGISTRY (`personId` → `resolveEntitySlug`), else **refused** — never the name. The router's rule-capture path passes `personId` so per-person rules keep landing after the fallback's removal.
|
|
8
|
+
- **Stranded grants are surfaced the moment they bite.** The dual-file trap — a mandate granted on the name-obvious note (`rahil-malde.md`) while the enforcer reads the person's hashed record slug — made grants silently do nothing. Escalation cards now carry a ⚠️ hint naming the file holding the dead grant and pointing at Verify / Make-a-rule, whenever the speaker's live mandate is empty but a same-named sibling note (that no other person owns) holds one. Detection only: the stranded text is never applied and never quoted.
|
|
9
|
+
- **Router plumbing for the fall-through:** the cross-channel dedup check is memoized per envelope object, so the adopted first message isn't swallowed by `handleText`'s re-check while true redeliveries (fresh envelope, same message id) still dedup globally.
|
|
10
|
+
- Regression coverage: `test-intro-adoption.js` joins the chain (adoption unit + through-the-router integration, fail-closed attestation, approval owner-mirror and in-place repair); `test-mandate-hygiene.js` pins the name-fallback refusal and personId resolution; `test-kazee-group-gate.js` pins the dm attestation and its fail-closed absence; `test-enforcer.js` pins the stranded-mandate hint and that the stranded text never reaches the live mandate.
|
|
11
|
+
|
|
3
12
|
## v3.3.1 — Message length can no longer kill the voice: chunked pocket TTS (re-cut carrying unpublished v3.3.0)
|
|
4
13
|
|
|
5
14
|
- **Long voice notes died at 60 seconds of synthesis.** `pocketToVoice` sent the ENTIRE cleaned text to the speech gateway as one request under `AbortSignal.timeout(60000)`. A long reply blew that window, the pocket route returned null, and on pods the fallback chain has nowhere to go (no ElevenLabs key, no macOS `say`) — so `say` bounced with "no TTS route available" and agents improvised manual per-paragraph chunking (sales-bot, 2026-08-19). Long text is now sentence-packed into ≤450-char chunks (`chunkForTTS`; run-on sentences hard-split at word boundaries so nothing can recreate the timeout), each chunk synthesized in its own 120s window with one retry (a transient gateway blip no longer wastes the chunks already done), and the wavs concatenated via ffmpeg's concat demuxer into ONE ogg/opus. Short text keeps the exact single-request behavior. Benefits every consumer of `textToVoice`/`textToVoiceFile`: `say`, `tts`, and whole voice-note replies; the voice channel's streamed per-sentence path is untouched. Regression pinned in `test-speech-loopback.js`: multiple gateway calls each inside the pack limit, same voice across chunks, lossless reassembly, temp cleanup, and chunker unit coverage.
|
|
@@ -301,6 +301,11 @@ class KazeeAdapter {
|
|
|
301
301
|
}
|
|
302
302
|
envelope.engaged = true;
|
|
303
303
|
envelope.surface = "group";
|
|
304
|
+
} else if (meta) {
|
|
305
|
+
// Adapter-attested 1:1 conversation — required before intro-flow may
|
|
306
|
+
// auto-adopt the chat for a known sender. Set only on a positive meta
|
|
307
|
+
// read: an unresolvable chat stays unattested (fail closed).
|
|
308
|
+
envelope.surface = "dm";
|
|
304
309
|
}
|
|
305
310
|
}
|
|
306
311
|
|
package/core/actions.js
CHANGED
|
@@ -89,7 +89,21 @@ function appendMandate(rec, line) {
|
|
|
89
89
|
function appendMandateLine(rec, lineText, log, opts) {
|
|
90
90
|
const entities = require("./entities");
|
|
91
91
|
const mandates = require("./mandates");
|
|
92
|
-
|
|
92
|
+
// Strictly slug-keyed: resolve through the record's entitySlug, else through
|
|
93
|
+
// the person record by id. NEVER name-keyed — a display name is spoofable,
|
|
94
|
+
// and a name-keyed upsert can land security text on a same-named OTHER
|
|
95
|
+
// person's note (the exact escalation the hashed auto-contact slugs exist
|
|
96
|
+
// to prevent).
|
|
97
|
+
let slug = String(rec.entitySlug || "").trim();
|
|
98
|
+
if (!slug && rec.personId) {
|
|
99
|
+
try {
|
|
100
|
+
const people = require("./people");
|
|
101
|
+
const person = people.findById(String(rec.personId));
|
|
102
|
+
slug = String((person && people.resolveEntitySlug(person)) || "").trim();
|
|
103
|
+
} catch (e) { slug = ""; }
|
|
104
|
+
}
|
|
105
|
+
if (!slug) return { ok: false, reason: "no linked contact record to hold the grant" };
|
|
106
|
+
const ent = entities.readEntity(slug);
|
|
93
107
|
const name = (ent && ent.name) || rec.personName;
|
|
94
108
|
if (!name) return { ok: false, reason: "no contact record" };
|
|
95
109
|
const coreText = mandates.splitExpiry(String(lineText || "")).text;
|
|
@@ -121,14 +135,12 @@ function appendMandateLine(rec, lineText, log, opts) {
|
|
|
121
135
|
return { ok: false, reason: "their mandate is full — trim it with /guardrails" };
|
|
122
136
|
}
|
|
123
137
|
const up = entities.upsertEntity({
|
|
124
|
-
slug
|
|
138
|
+
slug,
|
|
125
139
|
name,
|
|
126
140
|
relationship: (ent && ent.relationship) || "external",
|
|
127
141
|
mandate,
|
|
128
142
|
log,
|
|
129
143
|
});
|
|
130
|
-
// id is slug-scoped, so derive it from the entity the write actually landed
|
|
131
|
-
// on (legacy name-keyed upserts can resolve to a pre-existing slug).
|
|
132
144
|
const res = { ok: true, id: mandates.lineId(up.entity.slug, coreText), expiresAt: expiresAt || null };
|
|
133
145
|
if (refreshed) res.refreshed = true;
|
|
134
146
|
return res;
|
package/core/enforcer.js
CHANGED
|
@@ -336,7 +336,7 @@ function esc(s) {
|
|
|
336
336
|
// Framing: the owner is deciding whether the bot should ACT on a request from
|
|
337
337
|
// this person — the card leads with WHO is asking WHAT, then what the bot
|
|
338
338
|
// proposes to do about it, never with the bot's output as the headline.
|
|
339
|
-
async function postOwnerButtons(rec, owner, { kind, summary, reason }) {
|
|
339
|
+
async function postOwnerButtons(rec, owner, { kind, summary, reason, strandedMandate = "" }) {
|
|
340
340
|
const adapter = adapterFor(owner.adapter);
|
|
341
341
|
if (!adapter) return false;
|
|
342
342
|
const who = rec.personName || "An external contact";
|
|
@@ -380,6 +380,7 @@ async function postOwnerButtons(rec, owner, { kind, summary, reason }) {
|
|
|
380
380
|
} catch (e) { /* card still posts without the verify affordance */ }
|
|
381
381
|
const lines = [head, ``, ...askBlock, ...body,
|
|
382
382
|
unverifiedLine ? `\n${unverifiedLine}` : ``,
|
|
383
|
+
strandedMandate ? `\n⚠️ ${esc(strandedMandate)}` : ``,
|
|
383
384
|
reason ? `\nWhy I'm checking: ${esc(reason)}` : ``];
|
|
384
385
|
const yes = kind === "triage" ? "✅ Yes, handle it" : "✅ Approve once";
|
|
385
386
|
const no = kind === "triage" ? "⛔ No, decline" : "⛔ Deny";
|
|
@@ -450,6 +451,33 @@ async function escalate({
|
|
|
450
451
|
channelLabel = String((await originAdapter.channelLabel(String(speaker.channelId))) || "").trim();
|
|
451
452
|
}
|
|
452
453
|
} catch (e) {}
|
|
454
|
+
// Dual-file trap detection: a mandate granted on the name-obvious note while
|
|
455
|
+
// the enforcer reads this person's (hashed) record slug silently does
|
|
456
|
+
// nothing. Detect it at the moment it bites — the escalation the owner
|
|
457
|
+
// thought a grant had already prevented — and say where the grant actually
|
|
458
|
+
// lives. Detection only: the stranded text is NEVER applied (names are
|
|
459
|
+
// spoofable; only the owner links the two, via verify).
|
|
460
|
+
let strandedMandate = "";
|
|
461
|
+
try {
|
|
462
|
+
if (speaker && speaker.person && speaker.slug && !String(speaker.mandate || "").trim()) {
|
|
463
|
+
const entities = require("./entities");
|
|
464
|
+
const sibling = entities.slugify(speaker.person.name || "");
|
|
465
|
+
if (sibling && sibling !== speaker.slug) {
|
|
466
|
+
const other = entities.readEntity(sibling);
|
|
467
|
+
const text = other && other.type === "person"
|
|
468
|
+
? require("./mandates").activeMandateText(String((other.sections && other.sections.Mandate) || "")).trim()
|
|
469
|
+
: "";
|
|
470
|
+
if (text) {
|
|
471
|
+
const people = require("./people");
|
|
472
|
+
const holder = people.list().find((p) => p.id !== speaker.person.id && people.resolveEntitySlug(p) === sibling);
|
|
473
|
+
if (!holder) {
|
|
474
|
+
strandedMandate = `A mandate exists on ${sibling}.md, but ${speaker.person.name}'s record reads ${speaker.slug}.md — those grants have NO effect. Merge them with the Verify button (or /people verify), or re-add via Make a rule.`;
|
|
475
|
+
if (audit) audit.log("enforcer.stranded-mandate", { personId: speaker.person.id, slug: speaker.slug, sibling });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
} catch (e) { strandedMandate = ""; }
|
|
453
481
|
const rec = approvals.create({
|
|
454
482
|
tool: kind === "reply" ? "external-reply" : kind === "triage" ? "external-triage" : "external-action",
|
|
455
483
|
verb: "",
|
|
@@ -480,7 +508,7 @@ async function escalate({
|
|
|
480
508
|
inboundText,
|
|
481
509
|
channelLabel,
|
|
482
510
|
});
|
|
483
|
-
const posted = await postOwnerButtons(rec, owner, { kind, summary, reason });
|
|
511
|
+
const posted = await postOwnerButtons(rec, owner, { kind, summary, reason, strandedMandate });
|
|
484
512
|
if (!posted) {
|
|
485
513
|
approvals.decide(rec.id, "denied", "owner-unreachable");
|
|
486
514
|
if (audit) audit.log("enforcer.escalate.failed", { reason: "could not post to owner", id: rec.id });
|
package/core/intro-flow.js
CHANGED
|
@@ -98,11 +98,80 @@ async function notifyOwnerAboutIntro(intro) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// Legacy auth.json entry for a channel, created — or upgraded in place — so
|
|
102
|
+
// isChatAuthorized/isChatOwner agree with the people record. isOwner flows
|
|
103
|
+
// from people.isOwner: dropping it here is what demoted owners approved onto
|
|
104
|
+
// a second channel (isChatOwner reads this file, not the person).
|
|
105
|
+
function ensureAuthEntry(channelId, { name = null, username = null, isOwner = false } = {}) {
|
|
106
|
+
const auth = loadAuth();
|
|
107
|
+
const id = String(channelId);
|
|
108
|
+
const existing = auth.authorized.find((a) => String(a.chatId) === id);
|
|
109
|
+
if (existing) {
|
|
110
|
+
if (isOwner && existing.isOwner !== true) {
|
|
111
|
+
existing.isOwner = true;
|
|
112
|
+
saveAuth(auth);
|
|
113
|
+
}
|
|
114
|
+
return auth;
|
|
115
|
+
}
|
|
116
|
+
auth.authorized.push({
|
|
117
|
+
chatId: id,
|
|
118
|
+
name: name || "",
|
|
119
|
+
username: username || null,
|
|
120
|
+
isOwner: !!isOwner,
|
|
121
|
+
authorizedAt: new Date().toISOString(),
|
|
122
|
+
});
|
|
123
|
+
saveAuth(auth);
|
|
124
|
+
return auth;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// A sender whose transport user id already maps to a person is never
|
|
128
|
+
// interrogated: the id is adapter-attested (the same trust level as the
|
|
129
|
+
// KAZEE_OWNER_USER_ID owner match) — unlike display names, which are
|
|
130
|
+
// spoofable and are never matched. Only 1:1 conversations are adopted: the
|
|
131
|
+
// adapter must attest surface "dm", because auth.json and isChatOwner are
|
|
132
|
+
// per-CHANNEL, so adopting a multi-party room would hand every member the
|
|
133
|
+
// sender's standing. No attestation, no adoption (fail closed).
|
|
134
|
+
function adoptKnownSender(envelope, adapter, channelId) {
|
|
135
|
+
if (envelope.surface !== "dm") return null;
|
|
136
|
+
const senderId = envelope.userId != null ? String(envelope.userId) : "";
|
|
137
|
+
if (!senderId || senderId === channelId) return null;
|
|
138
|
+
const person = people.findByHandle(adapter, senderId);
|
|
139
|
+
if (!person) return null;
|
|
140
|
+
try {
|
|
141
|
+
people.linkHandle(person.id, {
|
|
142
|
+
adapter, channelId,
|
|
143
|
+
displayName: displayNameFromEnvelope(envelope),
|
|
144
|
+
approvedBy: "auto:transport-id",
|
|
145
|
+
});
|
|
146
|
+
} catch (e) {
|
|
147
|
+
return null; // handle conflict — leave the channel to the normal intro flow
|
|
148
|
+
}
|
|
149
|
+
ensureAuthEntry(channelId, {
|
|
150
|
+
name: person.name || displayNameFromEnvelope(envelope),
|
|
151
|
+
username: usernameFromEnvelope(envelope),
|
|
152
|
+
isOwner: person.isOwner === true,
|
|
153
|
+
});
|
|
154
|
+
const pending = intros.activeForChannel(adapter, channelId);
|
|
155
|
+
if (pending) intros.remove(pending.id);
|
|
156
|
+
audit.log("intro.auto-adopted", {
|
|
157
|
+
personId: person.id, adapter, channelId, senderId,
|
|
158
|
+
isOwner: person.isOwner === true,
|
|
159
|
+
resolvedIntroId: pending ? pending.id : null,
|
|
160
|
+
});
|
|
161
|
+
return person;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Returns true when the intro flow consumed the message (the router must not
|
|
165
|
+
// process it further); false ONLY when the sender was recognised by transport
|
|
166
|
+
// id and the channel auto-adopted — the router then handles the turn normally.
|
|
101
167
|
async function handleInbound(envelope, sendFn) {
|
|
102
168
|
intros.sweep();
|
|
103
169
|
const adapter = envAdapterType(envelope);
|
|
104
170
|
const channelId = String(envelope.channelId);
|
|
105
|
-
if (!adapter) return
|
|
171
|
+
if (!adapter) return true;
|
|
172
|
+
|
|
173
|
+
const known = adoptKnownSender(envelope, adapter, channelId);
|
|
174
|
+
if (known) return false;
|
|
106
175
|
|
|
107
176
|
let intro = intros.activeForChannel(adapter, channelId);
|
|
108
177
|
const text = String(envelope.text || "").trim();
|
|
@@ -200,7 +269,7 @@ async function handleInbound(envelope, sendFn) {
|
|
|
200
269
|
return true;
|
|
201
270
|
}
|
|
202
271
|
|
|
203
|
-
return
|
|
272
|
+
return true;
|
|
204
273
|
}
|
|
205
274
|
|
|
206
275
|
async function applyApproval(intro, by) {
|
|
@@ -227,20 +296,16 @@ async function applyApproval(intro, by) {
|
|
|
227
296
|
} else {
|
|
228
297
|
throw new Error("intro has no claim — owner approved a half-finished intro");
|
|
229
298
|
}
|
|
230
|
-
// Also write into auth.json so legacy isChatAuthorized + isChatOwner still
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
})
|
|
240
|
-
saveAuth(auth);
|
|
241
|
-
if (intro.adapter === "telegram") {
|
|
242
|
-
try { updateAuthorizedChatEnv(auth); } catch (e) {}
|
|
243
|
-
}
|
|
299
|
+
// Also write into auth.json so legacy isChatAuthorized + isChatOwner still
|
|
300
|
+
// work. isOwner mirrors the person record: an owner approved onto a new
|
|
301
|
+
// channel keeps owner status there (hardcoding false demoted Kazee owners).
|
|
302
|
+
const auth = ensureAuthEntry(intro.channelId, {
|
|
303
|
+
name: intro.displayName || person.name,
|
|
304
|
+
username: intro.username,
|
|
305
|
+
isOwner: person.isOwner === true,
|
|
306
|
+
});
|
|
307
|
+
if (intro.adapter === "telegram") {
|
|
308
|
+
try { updateAuthorizedChatEnv(auth); } catch (e) {}
|
|
244
309
|
}
|
|
245
310
|
intros.approve(intro.id, by);
|
|
246
311
|
audit.log("intro.approved", { introId: intro.id, personId: person.id, by });
|
package/core/router.js
CHANGED
|
@@ -63,15 +63,24 @@ async function deliverRouterError(error) {
|
|
|
63
63
|
// person is still caught once the owner's channels are unified.
|
|
64
64
|
const processedMessages = new Set();
|
|
65
65
|
function isDuplicate(envelope) {
|
|
66
|
+
// Memoized per envelope object: the unauthorized path checks dedup before
|
|
67
|
+
// intro handling, then falls through to the normal handlers (which check
|
|
68
|
+
// again) when a known sender's chat was auto-adopted — that second check
|
|
69
|
+
// must not mistake the envelope's own first check for a redelivery.
|
|
70
|
+
if (envelope._dedupSeen !== undefined) return envelope._dedupSeen;
|
|
66
71
|
const canonical = envelope.canonicalUserId || envelope.channelId;
|
|
67
72
|
const key = `${canonical}:${envelope.adapter?.id || envelope.adapter?.type}:${envelope.messageId}`;
|
|
68
|
-
if (processedMessages.has(key))
|
|
73
|
+
if (processedMessages.has(key)) {
|
|
74
|
+
envelope._dedupSeen = true;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
69
77
|
processedMessages.add(key);
|
|
70
78
|
if (processedMessages.size > 400) {
|
|
71
79
|
const arr = [...processedMessages];
|
|
72
80
|
processedMessages.clear();
|
|
73
81
|
arr.slice(-200).forEach((k) => processedMessages.add(k));
|
|
74
82
|
}
|
|
83
|
+
envelope._dedupSeen = false;
|
|
75
84
|
return false;
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -141,11 +150,12 @@ async function onMessage(envelope) {
|
|
|
141
150
|
}
|
|
142
151
|
|
|
143
152
|
if (!isChatAuthorized(envelope.channelId, envelope)) {
|
|
144
|
-
if (envelope.type
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
if (envelope.type !== "text") return;
|
|
154
|
+
if (isDuplicate(envelope)) return;
|
|
155
|
+
const consumed = await introFlow.handleInbound(envelope, (text) => send(text));
|
|
156
|
+
if (consumed) return;
|
|
157
|
+
// false = a transport-verified known sender was auto-adopted and the
|
|
158
|
+
// channel is authorized now — fall through and handle the turn.
|
|
149
159
|
}
|
|
150
160
|
|
|
151
161
|
// Auto-contact: an authorized speaker the people store doesn't know yet
|
|
@@ -604,7 +614,7 @@ async function handleText(envelope) {
|
|
|
604
614
|
// ("... for 14d" / "... until 2026-08-30").
|
|
605
615
|
const { text: coreRule, expiresAt } = require("./mandates").extractExpiry(ruleText);
|
|
606
616
|
const r = require("./actions").appendMandateLine(
|
|
607
|
-
{ entitySlug: pend.entitySlug, personName: pend.personName },
|
|
617
|
+
{ entitySlug: pend.entitySlug, personId: pend.personId, personName: pend.personName },
|
|
608
618
|
coreRule,
|
|
609
619
|
"owner added a per-person rule via approval card",
|
|
610
620
|
{ expiresAt },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.2",
|
|
4
4
|
"description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
|
|
5
5
|
"main": "bot.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"setup": "node setup.js",
|
|
12
12
|
"start": "node bot.js",
|
|
13
13
|
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-kazee-call-session.js",
|
|
14
|
-
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js"
|
|
14
|
+
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
package/test-enforcer.js
CHANGED
|
@@ -378,5 +378,36 @@ const extSpeaker = relationship.speakerFor("telegram", CH_EXT);
|
|
|
378
378
|
await runInChat({ ...grantCtx, triageApprovalId: escReply.id }, () => enforcer.vetReply(extSpeaker, "reply-kind-grant-payload"));
|
|
379
379
|
assert.ok(!/TURN GRANT/.test(grantPrompt), "only a triage-kind approval grants the turn");
|
|
380
380
|
|
|
381
|
+
// ── stranded-mandate hint (dual-file trap): a grant written to the
|
|
382
|
+
// name-obvious note while the speaker's record reads a hashed slug does
|
|
383
|
+
// nothing — the card must SAY so at the moment it bites, and the stranded
|
|
384
|
+
// text must never be applied or quoted. ──
|
|
385
|
+
const rahil = people.add({ name: "Rahil Malde" });
|
|
386
|
+
people.setEntitySlug(rahil.id, "rahil-malde-abc123");
|
|
387
|
+
people.linkHandle(rahil.id, { adapter: "telegram", channelId: "333" });
|
|
388
|
+
entities.upsertEntity({ slug: "rahil-malde-abc123", name: "Rahil Malde", type: "person", relationship: "external" });
|
|
389
|
+
entities.upsertEntity({ slug: "rahil-malde", name: "Rahil Malde", type: "person", relationship: "external",
|
|
390
|
+
mandate: "- May coordinate ticket triage." });
|
|
391
|
+
const strandedSpeaker = relationship.speakerFor("telegram", "333");
|
|
392
|
+
assert.strictEqual(String(strandedSpeaker.mandate || "").trim(), "",
|
|
393
|
+
"the stranded text never reaches the live mandate");
|
|
394
|
+
sends.length = 0;
|
|
395
|
+
const escStr = await enforcer.escalate({ speaker: strandedSpeaker, kind: "reply", payloadText: "p", summary: "s", reason: "r" });
|
|
396
|
+
assert.strictEqual(escStr.escalated, true, "stranded-mandate escalation still posts");
|
|
397
|
+
const strCard = sends.find((s) => s.channelId === CH_OWNER);
|
|
398
|
+
assert.ok(strCard && /rahil-malde\.md/.test(strCard.text) && /NO effect/.test(strCard.text),
|
|
399
|
+
"the owner card names the file holding the dead grant");
|
|
400
|
+
assert.ok(!/ticket triage/.test(strCard.text), "the stranded text itself stays off the card");
|
|
401
|
+
|
|
402
|
+
// A different person legitimately holding the name slug silences the hint —
|
|
403
|
+
// that mandate is THEIRS, not a stranded copy of this speaker's.
|
|
404
|
+
const rahil2 = people.add({ name: "Rahil M Other" });
|
|
405
|
+
people.setEntitySlug(rahil2.id, "rahil-malde");
|
|
406
|
+
sends.length = 0;
|
|
407
|
+
await enforcer.escalate({ speaker: relationship.speakerFor("telegram", "333"), kind: "reply", payloadText: "p", summary: "s", reason: "r" });
|
|
408
|
+
const strCard2 = sends.find((s) => s.channelId === CH_OWNER);
|
|
409
|
+
assert.ok(strCard2 && !/NO effect/.test(strCard2.text),
|
|
410
|
+
"no hint when the sibling note belongs to a real other person");
|
|
411
|
+
|
|
381
412
|
console.log("enforcer OK");
|
|
382
413
|
})().catch((e) => { console.error(e); process.exit(1); });
|
package/test-mandate-hygiene.js
CHANGED
|
@@ -63,13 +63,31 @@ assert.strictEqual(entities.readEntity("busy-contact").sections.Mandate, nearFul
|
|
|
63
63
|
// ── no record: nothing to write to → refused with a reason ──
|
|
64
64
|
const r4 = appendMandate({ entitySlug: "", personName: "" }, "anything");
|
|
65
65
|
assert.strictEqual(r4.ok, false, "no contact record → refused");
|
|
66
|
-
assert.ok(/no contact record/.test(r4.reason));
|
|
66
|
+
assert.ok(/no linked contact record/.test(r4.reason));
|
|
67
67
|
|
|
68
|
-
// ──
|
|
68
|
+
// ── name-only records are REFUSED, never name-keyed: a display name is
|
|
69
|
+
// spoofable, and a name-keyed upsert could land security text on a same-named
|
|
70
|
+
// OTHER person's note (v3.3.2 — this replaced the legacy fallback). ──
|
|
69
71
|
const r5 = appendMandate({ entitySlug: "", personName: "Legacy Larry" }, "asking build status");
|
|
70
|
-
assert.strictEqual(r5.ok,
|
|
71
|
-
assert.ok(/
|
|
72
|
-
|
|
72
|
+
assert.strictEqual(r5.ok, false, "name-only record is refused");
|
|
73
|
+
assert.ok(/no linked contact record/.test(r5.reason));
|
|
74
|
+
assert.strictEqual(entities.readEntity("legacy-larry"), null,
|
|
75
|
+
"no entity is ever minted from a bare display name");
|
|
76
|
+
|
|
77
|
+
// ── personId resolution: a record without an entitySlug resolves through the
|
|
78
|
+
// person REGISTRY (id → entitySlug), never through the spoofable name ──
|
|
79
|
+
const people = require("./core/people");
|
|
80
|
+
const harry = people.add({ name: "Hash Harry" });
|
|
81
|
+
people.setEntitySlug(harry.id, "hash-harry-9f9f9f");
|
|
82
|
+
entities.upsertEntity({ slug: "hash-harry-9f9f9f", name: "Hash Harry", relationship: "external" });
|
|
83
|
+
const r6 = appendMandate({ personId: harry.id, personName: "Spoofed Name" }, "asking build status");
|
|
84
|
+
assert.strictEqual(r6.ok, true, "personId-only record resolves via the registry");
|
|
85
|
+
assert.ok(/build status/.test(entities.readEntity("hash-harry-9f9f9f").sections.Mandate || ""),
|
|
86
|
+
"the grant lands on the person's registered slug");
|
|
87
|
+
assert.strictEqual(entities.readEntity("spoofed-name"), null,
|
|
88
|
+
"the transport display name never picks the target");
|
|
89
|
+
const r7 = appendMandate({ personId: "missing-person-id", personName: "Ghost" }, "anything");
|
|
90
|
+
assert.strictEqual(r7.ok, false, "an unresolvable personId is refused, not name-keyed");
|
|
73
91
|
|
|
74
92
|
// ── P3: capability-scoped grants — expiry, refresh, ids, revoke ──
|
|
75
93
|
const mandates = require("./core/mandates");
|
|
@@ -379,7 +379,17 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
379
379
|
// say) voiceless. Carries the unpublished 3.3.0 content plus the Kazee calls
|
|
380
380
|
// batch (trickle ICE + streaming TTS playback, ringing-on-invite/deferred
|
|
381
381
|
// publish, werift ICE rejection suppression). Plus this pin repair.
|
|
382
|
-
|
|
382
|
+
// 3.3.2 approved by Sumeet 2026-08-20 ("Okay, I'll go with your
|
|
383
|
+
// recommendations. Can you make sure you fix everything ... push everything
|
|
384
|
+
// out once the new versions have landed for the dockers update the entire
|
|
385
|
+
// fleet"): the identity/guardrail batch behind the Coders Africa incident —
|
|
386
|
+
// transport-verified sender adoption (a known transport user id in an
|
|
387
|
+
// adapter-attested DM is never interrogated; the router falls through and
|
|
388
|
+
// handles the adopted turn), applyApproval mirrors person.isOwner instead of
|
|
389
|
+
// demoting approved owners, and the mandate writer is strictly slug-keyed
|
|
390
|
+
// (name-keyed fallback removed) with stranded dual-file grants surfaced on
|
|
391
|
+
// the escalation card.
|
|
392
|
+
assert.strictEqual(pkg.version, "3.3.2", "release version must remain unchanged without explicit approval");
|
|
383
393
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
384
394
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
385
395
|
}
|