@dreamforgestudiollc/emet 0.2.4 → 0.2.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/.env.example +53 -0
- package/CHANGELOG.md +67 -3
- package/CHARTER.md +19 -7
- package/README.md +106 -21
- package/docs/AUTHORSHIP.md +3 -3
- package/docs/BOTS.md +4 -0
- package/docs/COVERAGE.md +2 -2
- package/docs/DEPLOY.md +9 -4
- package/docs/GUARDRAILS-AND-MONORAILS.md +2 -1
- package/docs/GUIDED-SETUP.md +8 -3
- package/docs/PERMISSIONS.md +6 -3
- package/docs/ROADMAP.md +25 -22
- package/docs/SETUP.md +8 -2
- package/docs/SUBKEYS.md +59 -0
- package/gems/artifact-index.json +12 -0
- package/gems/bot-team-backup/floor.json +15 -0
- package/gems/bot-team-backup/gem.json +43 -0
- package/gems/bot-team-backup/guides/BOT_TEAM_BACKUP.md +27 -0
- package/gems/bot-team-backup/guides/BOT_TEAM_RECOVERY.md +23 -0
- package/gems/fieldnote/floor.json +14 -0
- package/gems/fieldnote/gem.json +70 -0
- package/gems/fieldnote/runes/fieldnote.js +4 -0
- package/gems/lean-six-sigma/CHANGELOG.md +10 -0
- package/gems/lean-six-sigma/README.md +92 -0
- package/gems/lean-six-sigma/floor.json +31 -0
- package/gems/lean-six-sigma/gem.json +1296 -0
- package/gems/lean-six-sigma/guides/ASSUMPTIONS.md +45 -0
- package/gems/lean-six-sigma/guides/BELTS.md +52 -0
- package/gems/lean-six-sigma/guides/DMADV.md +56 -0
- package/gems/lean-six-sigma/guides/DMAIC.md +127 -0
- package/gems/lean-six-sigma/guides/FORMULAS.md +330 -0
- package/gems/lean-six-sigma/guides/LEAN.md +97 -0
- package/gems/lean-six-sigma/guides/TEMPLATES.md +279 -0
- package/gems/lean-six-sigma/runes/lss_capability.js +132 -0
- package/gems/lean-six-sigma/runes/lss_control_limits.js +240 -0
- package/gems/lean-six-sigma/runes/lss_dpmo_sigma.js +81 -0
- package/gems/lean-six-sigma/runes/lss_hypothesis.js +298 -0
- package/gems/lean-six-sigma/runes/lss_sample_size.js +81 -0
- package/gems/neurendipity/floor.json +65 -0
- package/gems/neurendipity/gem.json +42 -0
- package/gems/neurendipity/guides/AUTISTIC_BURNOUT.md +81 -0
- package/{guides → gems/neurendipity/guides}/NEURODIVERGENT_SUPPORT.md +17 -8
- package/guides/README.md +3 -0
- package/package.json +11 -5
- package/server.json +44 -4
- package/src/aleph.js +4 -4
- package/src/annotations.js +38 -3
- package/src/atlas-role.js +1 -1
- package/src/attribution.js +555 -0
- package/src/conformance.js +2 -2
- package/src/database.js +6 -5
- package/src/dispatch.js +116 -13
- package/src/env.js +54 -9
- package/src/floor.js +82 -41
- package/src/gaps.js +39 -6
- package/src/gem-check.js +1359 -0
- package/src/guides.js +31 -4
- package/src/host-reach.js +23 -0
- package/src/http-remote.js +46 -2
- package/src/index.js +9 -2
- package/src/layers.js +1 -1
- package/src/lessons-to-checks.js +3 -3
- package/src/logger.js +2 -1
- package/src/mongo-connect.js +45 -2
- package/src/oauth-provider.js +2 -2
- package/src/owner-approvals.js +281 -0
- package/src/persons.js +20 -0
- package/src/redact.js +10 -4
- package/src/request-log.js +1 -1
- package/src/rune-dispatch.js +360 -0
- package/src/rune-registry.js +71 -0
- package/src/rune-scope.js +42 -0
- package/src/scopes.js +3 -0
- package/src/session-graph.js +25 -23
- package/src/session.js +9 -9
- package/src/setup.js +297 -30
- package/src/socket.js +545 -0
- package/src/startup-pages.js +23 -6
- package/src/startup-payload.js +9 -6
- package/src/subkey-store.js +357 -0
- package/src/subkeys.js +947 -0
- package/src/tools.js +299 -62
- package/src/transcript-session.js +1 -1
- package/src/validation.js +26 -2
- package/src/version.js +1 -1
- package/templates/BOARD.md +2 -2
- package/templates/BOTHANDOFF.md +2 -2
- package/templates/CHARACTER.md +1 -1
- package/templates/HANDOFF.md +1 -1
- package/templates/IDENTITY.md +1 -1
- package/templates/INDEX.md +19 -19
- package/templates/INSTALL.md +1 -1
- package/templates/PROCEDURE.md +1 -1
- package/templates/ROSTER.md +2 -2
- package/templates/SEED.md +2 -2
- package/templates/STAGING.md +2 -2
- package/templates/STANDARD.md +5 -5
- package/templates/THREAD.md +1 -1
- package/templates/USER.md +4 -1
- package/verification/load-gate/corpus.json +1765 -0
- package/verification/load-gate/load-gate.mjs +1246 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMET - attribution per session, v2 (2026-10-12).
|
|
3
|
+
*
|
|
4
|
+
* Alex approved PROPOSAL.md with its 7 defaults;
|
|
5
|
+
* v1 (PR 32) was reverted after the reviewer's review (A1-A4). This version:
|
|
6
|
+
*
|
|
7
|
+
* STAMP vs RIGHTS. Several agents share one OAuth login (the Grok client
|
|
8
|
+
* 8xm-7ILLKEDFc3-QRxqlgw). A bot's declared tag is what its writes are
|
|
9
|
+
* stamped with (source_basis "declared", source_login = the login's tag),
|
|
10
|
+
* but a declared tag never gains rights beyond the login's tag (A3): every
|
|
11
|
+
* guard is run for BOTH tags and the write must pass both (rightsTags()).
|
|
12
|
+
*
|
|
13
|
+
* NEW CLIENTS (Alex, 2026-10-12). A tag is known when it is in
|
|
14
|
+
* EMET_SOURCE_TAGS, approved in the approvals record, or bound to a login at
|
|
15
|
+
* consent (the owner approved that at the consent page). The first write by
|
|
16
|
+
* any other tag is refused (read-limited) and queued in the approvals
|
|
17
|
+
* record `emet/CLIENT_APPROVALS.md` with who connected, the declared name,
|
|
18
|
+
* client_id and what it tried to write. The flag is routed:
|
|
19
|
+
* - to the chief-of-staff bot when EMET_COS_TAG is set (it sees the queue
|
|
20
|
+
* in emet_initialize / emet_status and approves with emet_client_approve);
|
|
21
|
+
* - to the owner directly when no COS is configured (single-instance users:
|
|
22
|
+
* one Claude or ChatGPT). The owner always may approve.
|
|
23
|
+
* A first, solo client on a store with no registry and no approvals is
|
|
24
|
+
* approved at once as the owner's primary: beginners meet no friction.
|
|
25
|
+
*
|
|
26
|
+
* SESSION TIES (A1, A4). Every write naming a session_id is checked against
|
|
27
|
+
* the tag that session belongs to, whatever was declared. The tie is cached
|
|
28
|
+
* in memory and rebuilt after a restart from the session's stored rows
|
|
29
|
+
* (transcript parts, documents, layer entries): if those rows carry another
|
|
30
|
+
* tag, the write is refused.
|
|
31
|
+
*
|
|
32
|
+
* OWNER (A2). Live owner tokens carry the owner's person id (e.g. "alex");
|
|
33
|
+
* older ones carry "owner" or nothing. All three are the owner.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { setting } from './env.js';
|
|
37
|
+
|
|
38
|
+
export const APPROVALS_DOC = 'emet/CLIENT_APPROVALS.md';
|
|
39
|
+
export const WRITE_TOOLS = new Set([
|
|
40
|
+
'save_to_layer', 'revise_memory', 'write_doc', 'patch_doc', 'retire_doc', 'rename_doc', 'restore_doc',
|
|
41
|
+
'accept_nonconformance', 'emet_transcript_append', 'save_transcript', 'revise_transcript', 'emet_session_close',
|
|
42
|
+
// the reviewer's re-review (2026-10-12): opening a session claims it; deciding a client is a write.
|
|
43
|
+
'emet_session_open', 'emet_client_approve',
|
|
44
|
+
// the reviewer's PR 47 review: setup writes durable identity and board docs.
|
|
45
|
+
'emet_setup_complete'
|
|
46
|
+
]);
|
|
47
|
+
/** Tools the strict login tag also covers. emet_initialize can provision, so a
|
|
48
|
+
* mismatched declared tag is refused under EMET_STRICT_LOGIN_TAG=1. It is kept out
|
|
49
|
+
* of WRITE_TOOLS so the default path (denied or multi-tag clients) can still start a session. */
|
|
50
|
+
export const STRICT_TAG_TOOLS = new Set([...WRITE_TOOLS, 'emet_initialize']);
|
|
51
|
+
export const BINDING_MAX = 5000;
|
|
52
|
+
export const SESSION_BINDINGS = new Map();
|
|
53
|
+
const APPROVALS_TTL_MS = 15000;
|
|
54
|
+
|
|
55
|
+
/** approvalsCache.data is null when not loaded; .missing / .broken describe the record. */
|
|
56
|
+
let approvalsCache = { at: 0, data: null, version: null, missing: false, broken: false };
|
|
57
|
+
let approvalsLock = Promise.resolve();
|
|
58
|
+
const seenSubkeySessions = new Set();
|
|
59
|
+
export function rememberSubkeySession(base) {
|
|
60
|
+
if (typeof base === 'string' && base) seenSubkeySessions.add(base);
|
|
61
|
+
}
|
|
62
|
+
export function _resetForTests() {
|
|
63
|
+
SESSION_BINDINGS.clear();
|
|
64
|
+
seenSubkeySessions.clear();
|
|
65
|
+
approvalsCache = { at: 0, data: null, version: null, missing: false, broken: false };
|
|
66
|
+
approvalsLock = Promise.resolve();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Off unless EMET_STRICT_LOGIN_TAG=1: a bound login tag cannot be overridden on a write. */
|
|
70
|
+
export function strictLoginTag() { return setting('STRICT_LOGIN_TAG') === '1'; }
|
|
71
|
+
|
|
72
|
+
export function cosTag() {
|
|
73
|
+
const t = String(setting('COS_TAG') ?? '').trim();
|
|
74
|
+
return t || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Same rule as session-graph.js isBotTag (kept local: no import cycle). */
|
|
78
|
+
export function isBotTagLocal(tag) {
|
|
79
|
+
if (typeof tag !== 'string' || !tag.trim() || tag.includes('/')) return false;
|
|
80
|
+
const listed = String(setting('RECORD_DOCS', '') || '').split(',').map((x) => x.trim()).filter(Boolean);
|
|
81
|
+
return listed.includes(`bots/${tag.trim()}/HANDOFF.md`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Approved tags as last loaded (sync; for the registry checks). */
|
|
85
|
+
export function approvedTags() {
|
|
86
|
+
const d = approvalsCache.data;
|
|
87
|
+
return d ? Object.keys(d.approved || {}) : [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function cachedApprovals() { return approvalsCache.data; }
|
|
91
|
+
|
|
92
|
+
export function emptyApprovals() { return { approved: {}, pending: {}, denied: {}, clients: {} }; }
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Remembered tags for one client_id (Alex 2026-10-12): approved clients keep
|
|
96
|
+
* their tag across logins. A shared client_id can carry several approved tags
|
|
97
|
+
* (one per bot), so this is a set. Retired entries (renamed away) are skipped.
|
|
98
|
+
* Approvals recorded before the map existed count through their request.
|
|
99
|
+
*/
|
|
100
|
+
export function rememberedFor(d, clientId) {
|
|
101
|
+
if (!d || !clientId) return [];
|
|
102
|
+
const out = new Set();
|
|
103
|
+
const row = d.clients && d.clients[clientId];
|
|
104
|
+
const retired = new Set();
|
|
105
|
+
if (row && row.tags) for (const [t, v] of Object.entries(row.tags)) (v && v.retired ? retired : out).add(t);
|
|
106
|
+
for (const [t, v] of Object.entries(d.approved || {})) {
|
|
107
|
+
const cid = v && (v.client_id || (v.request && v.request.client_id));
|
|
108
|
+
if (cid === clientId && !retired.has(t)) out.add(t);
|
|
109
|
+
}
|
|
110
|
+
return [...out];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function remember(d, clientId, tag, { declared_name = null, basis = null, at, renamed_from = null } = {}) {
|
|
114
|
+
if (!clientId || !tag) return;
|
|
115
|
+
d.clients = d.clients || {};
|
|
116
|
+
const row = d.clients[clientId] = d.clients[clientId] || { tags: {} };
|
|
117
|
+
row.tags = row.tags || {};
|
|
118
|
+
if (!row.tags[tag] || row.tags[tag].retired) row.tags[tag] = { declared_name: declared_name || tag, basis, since: at, ...(renamed_from ? { renamed_from } : {}) };
|
|
119
|
+
if (renamed_from && row.tags[renamed_from]) row.tags[renamed_from] = { ...row.tags[renamed_from], retired: true, renamed_to: tag, retired_at: at };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Strict: returns null when the record does not parse (fail closed, the reviewer 4). */
|
|
123
|
+
export function parseApprovals(content) {
|
|
124
|
+
const m = /```json\n([\s\S]*?)\n```/.exec(String(content || ''));
|
|
125
|
+
if (!m) return null;
|
|
126
|
+
try {
|
|
127
|
+
const d = JSON.parse(m[1]);
|
|
128
|
+
if (!d || typeof d !== 'object' || Array.isArray(d)) return null;
|
|
129
|
+
for (const k of ['approved', 'pending', 'denied', 'clients']) {
|
|
130
|
+
if (d[k] !== undefined && (typeof d[k] !== 'object' || Array.isArray(d[k]) || d[k] === null)) return null;
|
|
131
|
+
}
|
|
132
|
+
return { approved: d.approved || {}, pending: d.pending || {}, denied: d.denied || {}, clients: d.clients || {} };
|
|
133
|
+
} catch { return null; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function renderApprovals(d) {
|
|
137
|
+
const lines = ['# CLIENT APPROVALS', '',
|
|
138
|
+
'Server-kept record of connecting clients (EMET attribution v2). Server-only: hosts cannot write, patch, retire or rename it. Decide with `emet_client_approve`.',
|
|
139
|
+
`Routed to: ${cosTag() ? `chief-of-staff \`${cosTag()}\`` : 'the owner (no EMET_COS_TAG set)'}.`, '',
|
|
140
|
+
'```json', JSON.stringify(d, null, 2), '```', ''];
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** deps: { readDoc(doc_id) -> {content, version}|throws not found, writeDoc(doc_id, content, expectedVersion) -> {version} } */
|
|
145
|
+
export async function loadApprovals(deps, { fresh = false } = {}) {
|
|
146
|
+
if (!fresh && approvalsCache.at && Date.now() - approvalsCache.at < APPROVALS_TTL_MS) return approvalsCache;
|
|
147
|
+
let doc = null;
|
|
148
|
+
try { doc = await deps.readDoc(APPROVALS_DOC); }
|
|
149
|
+
catch (e) {
|
|
150
|
+
if (!/not found/i.test(String(e && e.message))) throw e;
|
|
151
|
+
}
|
|
152
|
+
if (!doc) approvalsCache = { at: Date.now(), data: emptyApprovals(), version: null, missing: true, broken: false };
|
|
153
|
+
else {
|
|
154
|
+
const data = parseApprovals(doc.content);
|
|
155
|
+
approvalsCache = data
|
|
156
|
+
? { at: Date.now(), data, version: doc.version ?? null, missing: false, broken: false }
|
|
157
|
+
: { at: Date.now(), data: null, version: doc.version ?? null, missing: false, broken: true };
|
|
158
|
+
}
|
|
159
|
+
return approvalsCache;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Compare-and-set (the reviewer 3): mutations run one at a time in this process, each
|
|
164
|
+
* re-reads the record and writes with the version it read; a version moved by
|
|
165
|
+
* another writer is retried on the fresh copy. `fn(data)` returns false to write nothing.
|
|
166
|
+
*/
|
|
167
|
+
export async function mutateApprovals(deps, fn, { tries = 4 } = {}) {
|
|
168
|
+
const run = async () => {
|
|
169
|
+
for (let i = 0; i < tries; i++) {
|
|
170
|
+
const cur = await loadApprovals(deps, { fresh: true });
|
|
171
|
+
if (cur.broken) throw refusal(`${APPROVALS_DOC} does not parse; client approvals fail closed. Nothing was written.`);
|
|
172
|
+
const data = JSON.parse(JSON.stringify(cur.data));
|
|
173
|
+
const changed = await fn(data, cur);
|
|
174
|
+
if (changed === false) return { data: cur.data, written: false };
|
|
175
|
+
try {
|
|
176
|
+
const out = await deps.writeDoc(APPROVALS_DOC, renderApprovals(data), cur.version);
|
|
177
|
+
approvalsCache = { at: Date.now(), data, version: out && out.version != null ? out.version : (cur.version || 0) + 1, missing: false, broken: false };
|
|
178
|
+
return { data, written: true };
|
|
179
|
+
} catch (e) {
|
|
180
|
+
if (!(e && e.versionConflict)) throw e;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw refusal(`${APPROVALS_DOC} kept changing under this write; try again. Nothing was written.`);
|
|
184
|
+
};
|
|
185
|
+
const p = approvalsLock.then(run, run);
|
|
186
|
+
approvalsLock = p.catch(() => {});
|
|
187
|
+
return p;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The person behind the token is the owner (person level: for D3 only). */
|
|
191
|
+
export function isOwnerSubject(subject, ownerId) {
|
|
192
|
+
return subject === null || subject === undefined || subject === 'owner' || (ownerId && subject === ownerId);
|
|
193
|
+
}
|
|
194
|
+
export function personIsOwner(actor, ownerId) {
|
|
195
|
+
const oid = ownerId || (actor && actor.ownerId) || null;
|
|
196
|
+
return !!actor && (actor.personId === 'owner' || (!!oid && actor.personId === oid));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The owner acting as the owner (the reviewer A2 re-review). Over HTTP the person must be
|
|
201
|
+
* the owner AND the call must carry the login's own tag (no declared or
|
|
202
|
+
* generated tag) AND that tag must not be a bot tag. Every bot on the owner's
|
|
203
|
+
* shared login is therefore not the owner. Stdio is the local owner.
|
|
204
|
+
*/
|
|
205
|
+
export function actorIsOwner(actor, ownerId) {
|
|
206
|
+
if (!actor || actor.transport !== 'http') return true;
|
|
207
|
+
if (!personIsOwner(actor, ownerId)) return false;
|
|
208
|
+
const login = typeof actor.sourceTag === 'string' && actor.sourceTag.trim() ? actor.sourceTag.trim() : null;
|
|
209
|
+
if (actor.sourceBasis && actor.sourceBasis !== 'login') return false;
|
|
210
|
+
if (actor.declaredTag) return false;
|
|
211
|
+
return !!login && !isBotTagLocal(login);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The configured chief of staff acting on its own login tag (never a declared copy of it). */
|
|
215
|
+
export function actorIsCos(actor) {
|
|
216
|
+
const cos = cosTag();
|
|
217
|
+
if (!cos || !actor || actor.transport !== 'http') return false;
|
|
218
|
+
const login = typeof actor.sourceTag === 'string' ? actor.sourceTag.trim() : '';
|
|
219
|
+
return login === cos && (!actor.sourceBasis || actor.sourceBasis === 'login') && !actor.declaredTag;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function declaredTagOf(args) {
|
|
223
|
+
const a = args || {};
|
|
224
|
+
const v = a.source ?? a.updated_by ?? a.retired_by ?? a.accepted_by ?? a.channel ??
|
|
225
|
+
(a.metadata && typeof a.metadata === 'object' ? a.metadata.source : undefined) ?? null;
|
|
226
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function sessionBaseOf(args) {
|
|
230
|
+
const a = args || {};
|
|
231
|
+
const v = a.session_id ?? (a.metadata && typeof a.metadata === 'object' ? a.metadata.session_id : undefined) ??
|
|
232
|
+
(a.metadata && a.metadata.derived_from && a.metadata.derived_from.session_id) ?? null;
|
|
233
|
+
if (typeof v !== 'string' || !v.trim()) return null;
|
|
234
|
+
return v.trim().replace(/_verbatim_part\d+$/, '').replace(/_part\d+$/, '');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function autoTag(clientId) {
|
|
238
|
+
const slug = String(clientId || 'client').toLowerCase().replace(/[^a-z0-9]+/g, '').slice(0, 8) || 'client';
|
|
239
|
+
return `auto-${slug}`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function refusal(msg) { const e = new Error(msg); e.attributionRefusal = true; return e; }
|
|
243
|
+
|
|
244
|
+
export function bindSession(base, tag, extra = {}) {
|
|
245
|
+
if (SESSION_BINDINGS.has(base)) return;
|
|
246
|
+
while (SESSION_BINDINGS.size >= BINDING_MAX) SESSION_BINDINGS.delete(SESSION_BINDINGS.keys().next().value);
|
|
247
|
+
SESSION_BINDINGS.set(base, { agent_tag: tag, ...extra, bound_at: new Date() });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Tags already on the session's stored rows (A4: survives a restart). Read only; fails closed (the reviewer A4). */
|
|
251
|
+
export async function storedSessionTags(db, base, layerCollections = []) {
|
|
252
|
+
if (!base) return [];
|
|
253
|
+
if (!db) throw refusal(`the store cannot be read to check who owns session ${base}; nothing was written.`);
|
|
254
|
+
const esc = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
255
|
+
const re = new RegExp(`^${esc}(_|$)`);
|
|
256
|
+
const tags = new Set();
|
|
257
|
+
const add = (t) => { if (typeof t === 'string' && t.trim()) tags.add(t.trim()); };
|
|
258
|
+
try {
|
|
259
|
+
for (const r of await db.collection('transcripts').find({ session_id: re }, { projection: { channel: 1 } }).limit(50).toArray()) add(r.channel);
|
|
260
|
+
for (const r of await db.collection('documents').find({ session_id: re }, { projection: { updated_by: 1 } }).limit(50).toArray()) add(r.updated_by);
|
|
261
|
+
for (const col of layerCollections) {
|
|
262
|
+
for (const r of await col.find({ 'metadata.derived_from.session_id': re }, { projection: { 'metadata.source': 1 } }).limit(50).toArray()) add(r.metadata && r.metadata.source);
|
|
263
|
+
}
|
|
264
|
+
} catch (e) {
|
|
265
|
+
throw refusal(`the store could not be read to check who owns session ${base} (${String(e && e.message || e).slice(0, 80)}); writes fail closed, nothing was written.`);
|
|
266
|
+
}
|
|
267
|
+
return [...tags];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The base a session open will claim: <local date>_<slug>, as emet_session_open names it. */
|
|
271
|
+
export function sessionOpenBase(args, now = new Date()) {
|
|
272
|
+
const slug = args && typeof args.slug === 'string' ? args.slug.trim() : '';
|
|
273
|
+
if (!slug) return null;
|
|
274
|
+
const tz = setting('TIMEZONE', 'America/New_York') || 'America/New_York';
|
|
275
|
+
let day;
|
|
276
|
+
try { day = now.toLocaleDateString('en-CA', { timeZone: tz }); } catch { day = now.toISOString().slice(0, 10); }
|
|
277
|
+
return `${day}_${slug}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Whether the store has seen any client but this one (a truly fresh store has not). */
|
|
281
|
+
async function storeHasOtherClients(db, prefix, actor) {
|
|
282
|
+
if (!db) return true;
|
|
283
|
+
try {
|
|
284
|
+
const rows = await db.collection(`${prefix}oauth_tokens`).find({}, { projection: { client_id: 1 } }).limit(200).toArray();
|
|
285
|
+
return rows.some((r) => r.client_id && r.client_id !== actor.clientId);
|
|
286
|
+
} catch { return true; }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Decide the tag for one HTTP call before the tool runs. Mutates the actor
|
|
291
|
+
* (declaredTag, sourceBasis, effectiveTag, approvalState). Throws an
|
|
292
|
+
* attributionRefusal on a refusal; nothing is written then.
|
|
293
|
+
* ctx: { db, prefix, ownerId, deps (approvals doc io), layerCollections, registry: string[], now }
|
|
294
|
+
*/
|
|
295
|
+
export async function preflight(ctx, actor, args, toolName) {
|
|
296
|
+
if (!actor || actor.transport !== 'http' || !actor.authenticated) return actor;
|
|
297
|
+
const { db, prefix = '', ownerId = null, deps = null, layerCollections = [], registry = [], now = new Date() } = ctx || {};
|
|
298
|
+
const bound = typeof actor.sourceTag === 'string' && actor.sourceTag.trim() ? actor.sourceTag.trim() : null;
|
|
299
|
+
const declared = declaredTagOf(args);
|
|
300
|
+
const isWrite = WRITE_TOOLS.has(toolName);
|
|
301
|
+
|
|
302
|
+
// Sub-keys (EMET_SUBKEYS=1, off by default). The handle check runs here,
|
|
303
|
+
// before the strict login check, and only when the flag is on. With the
|
|
304
|
+
// flag off the check is the handle shape and the sessions this process has
|
|
305
|
+
// already seen. It does not open the sub-key store.
|
|
306
|
+
if (setting('SUBKEYS') === '1') {
|
|
307
|
+
const { prepareSubkeyCall } = await import('./subkeys.js');
|
|
308
|
+
await prepareSubkeyCall({ ...ctx, now }, actor, args, toolName);
|
|
309
|
+
} else if (isWrite) {
|
|
310
|
+
const handle = args && typeof args.session_handle === 'string' ? args.session_handle.trim() : '';
|
|
311
|
+
if (handle.startsWith('emh_')) throw refusal('a sub-key session handle was presented and EMET_SUBKEYS is off. Nothing was written.');
|
|
312
|
+
const base = sessionBaseOf(args);
|
|
313
|
+
if (base && seenSubkeySessions.has(base)) throw refusal('session ' + base + ' is a sub-key session and EMET_SUBKEYS is off. Nothing was written.');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Strict login tags (EMET_STRICT_LOGIN_TAG=1, off by default): a login that
|
|
317
|
+
// carries a bound tag writes only under that tag. A different declared tag is
|
|
318
|
+
// refused, so one login cannot stamp another client's name. A valid sub-key
|
|
319
|
+
// handle for that tag is the exception. Off keeps attribution v2.
|
|
320
|
+
const subkeyCovers = actor.subkeySession === true && actor.subkeyTag && (!declared || declared === actor.subkeyTag);
|
|
321
|
+
if (STRICT_TAG_TOOLS.has(toolName) && bound && declared && declared !== bound && strictLoginTag() && !subkeyCovers) {
|
|
322
|
+
throw refusal(`this login is bound to "${bound}"; it may not write as "${declared}". Each client writes under its own login. Nothing was written.`);
|
|
323
|
+
}
|
|
324
|
+
if (declared && declared !== bound && db) {
|
|
325
|
+
// D3: a tag bound to another person's login (person level: the owner's
|
|
326
|
+
// tokens carry the owner id, "owner" or no subject).
|
|
327
|
+
const rows = await db.collection(`${prefix}oauth_tokens`).find({ source_tag: declared }, { projection: { subject: 1 } }).limit(20).toArray();
|
|
328
|
+
const ownerPerson = personIsOwner(actor, ownerId);
|
|
329
|
+
const foreign = rows.some((r) => ownerPerson ? !isOwnerSubject(r.subject, ownerId || actor.ownerId) : r.subject !== actor.personId);
|
|
330
|
+
if (foreign) throw refusal(`source "${declared}" is bound to another person's login, so this session may not write under it. Pass your own tag. Nothing was written.`);
|
|
331
|
+
}
|
|
332
|
+
if (actor.subkeySession && actor.subkeyTag && actor.subkeyTag !== bound && db) {
|
|
333
|
+
const rows = await db.collection(`${prefix}oauth_tokens`).find({ source_tag: actor.subkeyTag }, { projection: { subject: 1 } }).limit(20).toArray();
|
|
334
|
+
const ownerPerson = personIsOwner(actor, ownerId);
|
|
335
|
+
const foreign = rows.some((r) => ownerPerson ? !isOwnerSubject(r.subject, ownerId || actor.ownerId) : r.subject !== actor.personId);
|
|
336
|
+
if (foreign) throw refusal(`source "${actor.subkeyTag}" is bound to another person's login, so this session may not write under it. Pass your own tag. Nothing was written.`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (actor.subkeySession && actor.subkeyTag) {
|
|
340
|
+
actor.declaredTag = null;
|
|
341
|
+
actor.sourceBasis = 'subkey';
|
|
342
|
+
actor.effectiveTag = actor.subkeyTag;
|
|
343
|
+
actor.approvalState = 'subkey';
|
|
344
|
+
const base = !isWrite ? null : (toolName === 'emet_session_open' ? sessionOpenBase(args, now) : sessionBaseOf(args));
|
|
345
|
+
if (base && actor.effectiveTag) {
|
|
346
|
+
let row = SESSION_BINDINGS.get(base);
|
|
347
|
+
if (!row) {
|
|
348
|
+
const stored = await storedSessionTags(db, base, layerCollections);
|
|
349
|
+
const other = stored.find((t) => t !== actor.effectiveTag);
|
|
350
|
+
if (other) row = { agent_tag: other };
|
|
351
|
+
}
|
|
352
|
+
if (row && row.agent_tag !== actor.effectiveTag) {
|
|
353
|
+
throw refusal(`session ${base} belongs to "${row.agent_tag}"; "${actor.effectiveTag}" may not write in it. Open your own session. Nothing was written.`);
|
|
354
|
+
}
|
|
355
|
+
bindSession(base, actor.effectiveTag, { basis: 'subkey', key_id: actor.subkeyId || null, client_id: actor.clientId || null, person_id: actor.personId || null, login_tag: bound });
|
|
356
|
+
}
|
|
357
|
+
return actor;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Remembered tags for this client_id (Alex 2026-10-12).
|
|
361
|
+
let remembered = [];
|
|
362
|
+
if (deps && actor.clientId && actor.clientId !== 'http:visitor') {
|
|
363
|
+
const cur0 = await loadApprovals(deps);
|
|
364
|
+
if (cur0.data) remembered = rememberedFor(cur0.data, actor.clientId);
|
|
365
|
+
}
|
|
366
|
+
actor.rememberedTags = remembered;
|
|
367
|
+
if (!declared && !bound && remembered.length > 1 && isWrite) {
|
|
368
|
+
throw refusal(`this client is remembered under several tags (${remembered.join(', ')}); pass "source" with yours. Nothing was written.`);
|
|
369
|
+
}
|
|
370
|
+
if (declared && declared !== bound && remembered.length && !remembered.includes(declared) && !registry.includes(declared)) {
|
|
371
|
+
// A different tag than the remembered one: a rename, which needs the owner.
|
|
372
|
+
actor.declaredTag = declared; actor.sourceBasis = 'declared'; actor.effectiveTag = declared; actor.approvalState = 'rename_pending';
|
|
373
|
+
if (isWrite) {
|
|
374
|
+
await mutateApprovals(deps, (d) => {
|
|
375
|
+
if (d.pending[declared]) return false;
|
|
376
|
+
d.pending[declared] = {
|
|
377
|
+
kind: 'rename', rename_from: remembered, first_seen: now.toISOString(), declared_name: declared, basis: 'declared',
|
|
378
|
+
client_id: actor.clientId || null, grant_id: actor.grantId || null, person_id: actor.personId || null, login_tag: bound,
|
|
379
|
+
tool: toolName, what: summarizeWrite(toolName, args), routed_to: cosTag() || 'owner'
|
|
380
|
+
};
|
|
381
|
+
return true;
|
|
382
|
+
});
|
|
383
|
+
throw refusal(`this client is remembered as ${remembered.map((t) => `"${t}"`).join(', ')}; "${declared}" is a rename and waits for ${cosTag() ? `the chief of staff (${cosTag()})` : 'the owner'} to approve it. Use your remembered tag. Nothing was written.`);
|
|
384
|
+
}
|
|
385
|
+
return actor;
|
|
386
|
+
}
|
|
387
|
+
const keptAuto = !declared && !bound && remembered.length === 1 ? remembered[0] : null;
|
|
388
|
+
const effective = declared || bound || keptAuto || (actor.clientId && actor.clientId !== 'http:visitor' ? autoTag(actor.clientId) : null);
|
|
389
|
+
const basis = declared && declared !== bound ? 'declared' : (bound ? 'login' : (keptAuto ? 'remembered' : (effective ? 'auto' : null)));
|
|
390
|
+
actor.declaredTag = basis === 'declared' || basis === 'auto' || basis === 'remembered' ? effective : null;
|
|
391
|
+
actor.sourceBasis = basis;
|
|
392
|
+
actor.effectiveTag = effective;
|
|
393
|
+
actor.approvalState = basis === 'login' ? 'login' : (registry.includes(effective) ? 'registered' : null);
|
|
394
|
+
|
|
395
|
+
// New-client gate (Alex 2026-10-12). Bound tags were approved at consent;
|
|
396
|
+
// registered tags by the owner's env. Everything else needs the record.
|
|
397
|
+
if (deps && effective && basis !== 'login' && !registry.includes(effective)) {
|
|
398
|
+
const cur = await loadApprovals(deps);
|
|
399
|
+
if (cur.broken) {
|
|
400
|
+
actor.approvalState = 'unreadable';
|
|
401
|
+
if (isWrite) throw refusal(`${APPROVALS_DOC} does not parse, so new clients fail closed. Nothing was written.`);
|
|
402
|
+
return actor;
|
|
403
|
+
}
|
|
404
|
+
const appr = cur.data;
|
|
405
|
+
if (Object.prototype.hasOwnProperty.call(appr.approved, effective)) actor.approvalState = 'approved';
|
|
406
|
+
else if (Object.prototype.hasOwnProperty.call(appr.denied, effective)) {
|
|
407
|
+
actor.approvalState = 'denied';
|
|
408
|
+
if (isWrite) throw refusal(`"${effective}" was denied as a client on this store; it may read only. Nothing was written.`);
|
|
409
|
+
} else {
|
|
410
|
+
const fresh = cur.missing && registry.length === 0 && personIsOwner(actor, ownerId) && !(await storeHasOtherClients(db, prefix, actor));
|
|
411
|
+
if (fresh && isWrite) {
|
|
412
|
+
await mutateApprovals(deps, (d, c) => {
|
|
413
|
+
if (!c.missing || Object.keys(d.approved).length) return false; // someone else got there first
|
|
414
|
+
d.approved[effective] = { by: 'auto: first solo client on a fresh store', at: now.toISOString(), primary: true, client_id: actor.clientId || null, person_id: actor.personId || null };
|
|
415
|
+
remember(d, actor.clientId, effective, { declared_name: declared || effective, basis, at: now.toISOString() });
|
|
416
|
+
return true;
|
|
417
|
+
});
|
|
418
|
+
if (!approvalsCache.data.approved[effective]) throw refusal(`"${effective}" is a new client and the store already has a primary; it waits for approval. Nothing was written.`);
|
|
419
|
+
actor.approvalState = 'approved';
|
|
420
|
+
} else {
|
|
421
|
+
actor.approvalState = appr.pending[effective] ? 'pending' : 'new';
|
|
422
|
+
if (isWrite) {
|
|
423
|
+
// A missing record on a store that has other clients is never a fresh
|
|
424
|
+
// store: no auto-approve (fail closed). Queuing creates the record.
|
|
425
|
+
await mutateApprovals(deps, (d) => {
|
|
426
|
+
if (d.pending[effective] || d.approved[effective] || d.denied[effective]) return false;
|
|
427
|
+
d.pending[effective] = {
|
|
428
|
+
first_seen: now.toISOString(), declared_name: effective, basis,
|
|
429
|
+
client_id: actor.clientId || null, grant_id: actor.grantId || null, person_id: actor.personId || null, login_tag: bound,
|
|
430
|
+
tool: toolName, what: summarizeWrite(toolName, args), routed_to: cosTag() || 'owner'
|
|
431
|
+
};
|
|
432
|
+
return true;
|
|
433
|
+
});
|
|
434
|
+
actor.approvalState = 'pending';
|
|
435
|
+
throw refusal(`"${effective}" is a new client on this store and is read-limited until ${cosTag() ? `the chief of staff (${cosTag()})` : 'the owner'} approves it with emet_client_approve. The request is queued; nothing was written.`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Session tie on every write (A1), rebuilt from stored rows after a restart (A4).
|
|
442
|
+
// emet_session_open claims the session it is about to open.
|
|
443
|
+
const base = !isWrite ? null : (toolName === 'emet_session_open' ? sessionOpenBase(args, now) : sessionBaseOf(args));
|
|
444
|
+
if (base && effective) {
|
|
445
|
+
let row = SESSION_BINDINGS.get(base);
|
|
446
|
+
if (!row) {
|
|
447
|
+
const stored = await storedSessionTags(db, base, layerCollections);
|
|
448
|
+
const other = stored.find((t) => t !== effective);
|
|
449
|
+
if (other) row = { agent_tag: other };
|
|
450
|
+
}
|
|
451
|
+
if (row && row.agent_tag !== effective) {
|
|
452
|
+
throw refusal(`session ${base} belongs to "${row.agent_tag}"; "${effective}" may not write in it. Open your own session. Nothing was written.`);
|
|
453
|
+
}
|
|
454
|
+
bindSession(base, effective, { client_id: actor.clientId || null, person_id: actor.personId || null, login_tag: bound });
|
|
455
|
+
}
|
|
456
|
+
return actor;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function summarizeWrite(toolName, args) {
|
|
460
|
+
const a = args || {};
|
|
461
|
+
const parts = [toolName];
|
|
462
|
+
if (a.doc_id) parts.push(`doc ${a.doc_id}`);
|
|
463
|
+
if (a.layer) parts.push(`layer ${a.layer}`);
|
|
464
|
+
if (a.session_id) parts.push(`session ${a.session_id}`);
|
|
465
|
+
const text = a.content || a.new_str || a.assistant_message || '';
|
|
466
|
+
if (text) parts.push(`"${String(text).slice(0, 200)}"`);
|
|
467
|
+
return parts.join(' · ');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** The approvals view for a startup or status payload, or null for callers who do not see it. */
|
|
471
|
+
export function approvalsView(data, actor, ownerId) {
|
|
472
|
+
if (!data) return null;
|
|
473
|
+
const cos = cosTag();
|
|
474
|
+
const isCos = actorIsCos(actor);
|
|
475
|
+
const owner = actorIsOwner(actor, ownerId);
|
|
476
|
+
if (!(isCos || owner)) return null;
|
|
477
|
+
const pending = Object.entries(data.pending || {}).map(([t, p]) => ({ tag: t, ...p }));
|
|
478
|
+
// Routed to the COS when one is set; the owner then sees the count.
|
|
479
|
+
if (cos && !isCos) return { routed_to: cos, pending_count: pending.length };
|
|
480
|
+
return { routed_to: cos || 'owner', pending, approve_with: 'emet_client_approve {"tag": "<tag>", "decision": "approve"|"deny"}' };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* emet_client_approve. Only the owner acting as the owner, or the configured
|
|
485
|
+
* COS on its own login tag; never the client deciding itself (the reviewer 1).
|
|
486
|
+
* Idempotent: the same decision again writes nothing (the reviewer 2).
|
|
487
|
+
*/
|
|
488
|
+
export async function clientApprove(deps, actor, ownerId, { tag, decision, reason, as, from } = {}, _registry = [], now = new Date()) {
|
|
489
|
+
const t = typeof tag === 'string' ? tag.trim() : '';
|
|
490
|
+
if (!t) throw refusal('emet_client_approve: pass "tag", the pending client to decide. Nothing was changed.');
|
|
491
|
+
if (decision !== 'approve' && decision !== 'deny') throw refusal('emet_client_approve: "decision" must be "approve" or "deny". Nothing was changed.');
|
|
492
|
+
const me = actor && (actor.effectiveTag || actor.sourceTag) || null;
|
|
493
|
+
if (actor && actor.transport === 'http' && me === t) throw refusal('emet_client_approve: a client may not decide its own approval. Nothing was changed.');
|
|
494
|
+
const ownerOk = actorIsOwner(actor, ownerId);
|
|
495
|
+
const cosOk = actorIsCos(actor);
|
|
496
|
+
if (!(ownerOk || cosOk)) throw refusal(`emet_client_approve: only the owner${cosTag() ? ` or the chief of staff (${cosTag()})` : ''} may decide new clients. Nothing was changed.`);
|
|
497
|
+
if (setting('SUBKEYS') === '1') {
|
|
498
|
+
const { decideSubkeyIfPending } = await import('./subkeys.js');
|
|
499
|
+
const decided = await decideSubkeyIfPending(deps, actor, ownerId, { tag: t, decision, reason }, now);
|
|
500
|
+
if (decided) return decided;
|
|
501
|
+
}
|
|
502
|
+
const by = ownerOk ? `owner (${actor && actor.personId || 'local'})` : `cos ${cosTag()}`;
|
|
503
|
+
let result = null;
|
|
504
|
+
await mutateApprovals(deps, (d) => {
|
|
505
|
+
// the reviewer round 3: whatever path made the caller "owner" or "COS", it may never
|
|
506
|
+
// decide a request that its own connection queued (same client_id or same
|
|
507
|
+
// grant), nor a tag equal to its own. A bot on a shared login that omits
|
|
508
|
+
// `source` looks like the login; this catches it.
|
|
509
|
+
const req = d.pending[t] || (d.approved[t] && d.approved[t].request) || (d.denied[t] && d.denied[t].request) || null;
|
|
510
|
+
if (actor && actor.transport === 'http' && req) {
|
|
511
|
+
const sameClient = !!req.client_id && req.client_id === (actor.clientId || null);
|
|
512
|
+
const sameGrant = !!req.grant_id && req.grant_id === (actor.grantId || null);
|
|
513
|
+
if (sameClient || sameGrant) throw refusal('emet_client_approve: this connection queued the request for "' + t + '", so it may not decide it. Decide from another client. Nothing was changed.');
|
|
514
|
+
}
|
|
515
|
+
// the reviewer delta M2: a client_id that already has approved tags needs an explicit
|
|
516
|
+
// `as` ('add' keeps every sibling, 'rename' retires one named tag).
|
|
517
|
+
const cidReq = (d.pending[t] && d.pending[t].client_id) || null;
|
|
518
|
+
const siblings = decision === 'approve' && cidReq ? rememberedFor(d, cidReq).filter((x) => x !== t) : [];
|
|
519
|
+
let retireFrom = null;
|
|
520
|
+
if (siblings.length && !(d.approved[t] && !d.pending[t])) {
|
|
521
|
+
if (as !== 'add' && as !== 'rename') throw refusal(`emet_client_approve: client ${cidReq} already has approved tags (${siblings.join(', ')}). Pass "as": "add" to keep them and add "${t}", or "as": "rename" to replace one. Nothing was changed.`);
|
|
522
|
+
if (as === 'rename') {
|
|
523
|
+
retireFrom = siblings.length === 1 ? siblings[0] : (typeof from === 'string' && siblings.includes(from.trim()) ? from.trim() : null);
|
|
524
|
+
if (!retireFrom) throw refusal(`emet_client_approve: client ${cidReq} has several approved tags (${siblings.join(', ')}); a rename must name the one it replaces with "from". Nothing was changed.`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const into = decision === 'approve' ? d.approved : d.denied;
|
|
528
|
+
const other = decision === 'approve' ? d.denied : d.approved;
|
|
529
|
+
if (into[t] && !d.pending[t]) { result = { tag: t, decision, by: into[t].by, at: into[t].at, unchanged: true }; return false; }
|
|
530
|
+
const pend = d.pending[t] || (other[t] && other[t].request) || null;
|
|
531
|
+
delete d.pending[t];
|
|
532
|
+
delete other[t];
|
|
533
|
+
into[t] = { by, at: now.toISOString(), reason: reason || null, ...(pend ? { request: pend } : {}) };
|
|
534
|
+
if (decision === 'approve' && pend && pend.client_id) {
|
|
535
|
+
remember(d, pend.client_id, t, { declared_name: pend.declared_name || t, basis: pend.basis || null, at: into[t].at, renamed_from: retireFrom });
|
|
536
|
+
}
|
|
537
|
+
result = { tag: t, decision, by, at: into[t].at };
|
|
538
|
+
return true;
|
|
539
|
+
});
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** What emet_initialize tells a client about its remembered tag (Alex 2026-10-12). */
|
|
544
|
+
export function rememberedView(actor) {
|
|
545
|
+
const tags = actor && Array.isArray(actor.rememberedTags) ? actor.rememberedTags : [];
|
|
546
|
+
if (!tags.length) return null;
|
|
547
|
+
const declared = actor.declaredTag || null;
|
|
548
|
+
const use = declared && tags.includes(declared) ? declared : (tags.length === 1 ? tags[0] : null);
|
|
549
|
+
return {
|
|
550
|
+
remembered_tags: tags, use_tag: use,
|
|
551
|
+
instruction: use
|
|
552
|
+
? `This client is remembered as "${use}". Pass source "${use}" on every write; a different tag is a rename and needs the owner's approval.`
|
|
553
|
+
: `This client is remembered under several tags (${tags.join(', ')}). Pass source with the one that is yours; a new tag needs the owner's approval.`
|
|
554
|
+
};
|
|
555
|
+
}
|
package/src/conformance.js
CHANGED
|
@@ -369,8 +369,8 @@ function idCell(row, idIndex) {
|
|
|
369
369
|
* a closed row named in a note, an item cross-referenced in a next action -
|
|
370
370
|
* read as a row of its own, so the carry-forward check reported it missing
|
|
371
371
|
* from the next revision. Found by running the validator against the reference
|
|
372
|
-
* install's own board, 2026-09-11:
|
|
373
|
-
* revision and appeared only inside another row's text.
|
|
372
|
+
* install's own board, 2026-09-11: board 012 and board 050 were both closed on the
|
|
373
|
+
* prior revision and appeared only inside another row's text.
|
|
374
374
|
*
|
|
375
375
|
* The failure direction matters and is recorded so no one "fixes" this by
|
|
376
376
|
* loosening it: the bug made the check NOISY, never permissive. Nothing passed
|
package/src/database.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import './env.js';
|
|
26
|
-
import { connectMongo } from './mongo-connect.js';
|
|
26
|
+
import { connectMongo, bindSharedClient } from './mongo-connect.js';
|
|
27
27
|
import path from 'path';
|
|
28
28
|
|
|
29
29
|
import {
|
|
@@ -230,7 +230,7 @@ const FIELD_SET = new Set([
|
|
|
230
230
|
'id', 'timestamp', 'content', 'context', 'emotional_intensity', 'importance', 'metadata',
|
|
231
231
|
'superseded_by', 'valid_until', 'is_live',
|
|
232
232
|
'source_session_id', 'source_exchange_start', 'source_exchange_end', 'derived_from_ids',
|
|
233
|
-
// sha256 of content, written at save time (2026-09-05). Before this the
|
|
233
|
+
// sha256 of content, written at save time (EMET ref 12, 2026-09-05). Before this the
|
|
234
234
|
// digest was computed for the read-back receipt and then discarded, so no layer
|
|
235
235
|
// entry could ever be re-verified after the moment it was written.
|
|
236
236
|
'sha256',
|
|
@@ -258,7 +258,7 @@ const FIELD_SET = new Set([
|
|
|
258
258
|
// `metadata.superseded_by` matched ZERO documents. Confirmed by probe 2026-08-24.
|
|
259
259
|
// The fix therefore PROMOTED the two fields to real top-level columns, which are
|
|
260
260
|
// queryable and indexable, and filters on those. Since 2026-09-05 metadata is a
|
|
261
|
-
// real subdocument and its copy IS visible to Mongo - but the promoted
|
|
261
|
+
// real subdocument (EMET ref 6) and its copy IS visible to Mongo - but the promoted
|
|
262
262
|
// columns remain the fields the liveness predicate reads: they are indexed, and
|
|
263
263
|
// the vector-search filter can only see top-level booleans. The JS-level checks
|
|
264
264
|
// in tools.js remain as a second line of defence.
|
|
@@ -379,6 +379,7 @@ export class EmetDatabase {
|
|
|
379
379
|
}
|
|
380
380
|
this.client = client;
|
|
381
381
|
this.db = client.db(MONGODB_DB);
|
|
382
|
+
bindSharedClient(client);
|
|
382
383
|
this.log('info', `Connected to MongoDB database '${MONGODB_DB}'`);
|
|
383
384
|
})();
|
|
384
385
|
|
|
@@ -437,7 +438,7 @@ export class EmetDatabase {
|
|
|
437
438
|
}
|
|
438
439
|
|
|
439
440
|
/**
|
|
440
|
-
* THE layer insert, native (2026-09-27, code audit 1.7 stage 1 -
|
|
441
|
+
* THE layer insert, native (2026-09-27, code audit 1.7 stage 1 - Alex: "if SQL
|
|
441
442
|
* is the old and needs to be updated then update it"). Takes the document as
|
|
442
443
|
* a document. Every key must be a known layer field; the id is allocated
|
|
443
444
|
* here. The INSERT-string shim it replaced went with the SQL translator in
|
|
@@ -468,7 +469,7 @@ export class EmetDatabase {
|
|
|
468
469
|
|
|
469
470
|
/**
|
|
470
471
|
* Where a transcript session stands, for the provenance auto-stamp
|
|
471
|
-
* (2026-09-29,
|
|
472
|
+
* (2026-09-29, procedural ref 398). Read-only. Resolves a base id or any part id
|
|
472
473
|
* to its base and the highest exchange index stored across every live part;
|
|
473
474
|
* 0 when nothing is stored yet (a session opened but not yet appended).
|
|
474
475
|
*/
|