@unson/brainbase-mcp 0.43.0 → 0.44.1
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/contracts/canonical-task/operation-schema.sql +58 -0
- package/contracts/canonical-task/search-indexes.sql +10 -0
- package/contracts/canonical-task/store-schema.sql +77 -0
- package/dist/automation-run.d.ts +213 -0
- package/dist/automation-run.js +479 -0
- package/dist/canonical-task-operations.d.ts +205 -0
- package/dist/canonical-task-operations.js +700 -0
- package/dist/canonical-task-postgres.d.ts +139 -0
- package/dist/canonical-task-postgres.js +554 -0
- package/dist/canonical-task-service.d.ts +13 -0
- package/dist/canonical-task-service.js +55 -3
- package/dist/canonical-task-store-config.d.ts +31 -0
- package/dist/canonical-task-store-config.js +53 -0
- package/dist/config-parser.d.ts +298 -0
- package/dist/config-parser.js +593 -0
- package/dist/connected-onboarding.d.ts +57 -0
- package/dist/connected-onboarding.js +37 -0
- package/dist/durable-wait-http.d.ts +3 -1
- package/dist/durable-wait-http.js +73 -6
- package/dist/durable-waits.d.ts +93 -3
- package/dist/durable-waits.js +320 -8
- package/dist/honcho.d.ts +25 -0
- package/dist/honcho.js +81 -0
- package/dist/judgment-event-adapter.d.ts +72 -0
- package/dist/judgment-event-adapter.js +114 -0
- package/dist/judgment-event-outbox.d.ts +182 -0
- package/dist/judgment-event-outbox.js +568 -0
- package/dist/judgment-execution-outcome.d.ts +46 -0
- package/dist/judgment-execution-outcome.js +149 -0
- package/dist/judgment-host.d.ts +18 -7
- package/dist/judgment-host.js +82 -34
- package/dist/judgment-lifecycle-core.d.ts +198 -0
- package/dist/judgment-lifecycle-core.js +464 -0
- package/dist/judgment-node-evidence.d.ts +57 -0
- package/dist/judgment-node-evidence.js +297 -0
- package/dist/judgment-runtime-resolution.d.ts +264 -0
- package/dist/judgment-runtime-resolution.js +1248 -0
- package/dist/judgment-runtime-resolution.public.d.ts +255 -0
- package/dist/knowledge-event.d.ts +28 -0
- package/dist/knowledge-event.js +114 -0
- package/dist/mcp-runtime.d.ts +75 -0
- package/dist/mcp-runtime.js +188 -0
- package/dist/meeting-automation-runtime.d.ts +102 -0
- package/dist/meeting-automation-runtime.js +249 -0
- package/dist/meeting-automation.d.ts +155 -0
- package/dist/meeting-automation.js +341 -0
- package/dist/meeting-minutes-context-receipt.d.ts +91 -0
- package/dist/meeting-minutes-context-receipt.js +280 -0
- package/dist/meeting-source-sync.d.ts +89 -0
- package/dist/meeting-source-sync.js +230 -0
- package/dist/mesh.d.ts +58 -0
- package/dist/mesh.js +134 -0
- package/dist/onboarding-runtime.d.ts +47 -0
- package/dist/onboarding-runtime.js +4719 -0
- package/dist/routine-cycle.d.ts +51 -0
- package/dist/routine-cycle.js +153 -0
- package/dist/routine-liveness.d.ts +49 -0
- package/dist/routine-liveness.js +227 -0
- package/dist/routine-runtime.d.ts +101 -0
- package/dist/routine-runtime.js +237 -0
- package/dist/routines.d.ts +3 -0
- package/dist/routines.js +3 -0
- package/dist/run-receipt.d.ts +40 -0
- package/dist/run-receipt.js +635 -0
- package/dist/runtime-host.d.ts +64 -0
- package/dist/runtime-host.js +70 -0
- package/dist/world-work.d.ts +16 -0
- package/package.json +115 -3
- package/ui/meeting-minutes.css +122 -0
- package/ui/meeting-minutes.js +316 -0
- package/ui/objective-editor.js +47 -11
- package/ui/world/world-district.js +18 -3
- package/ui/world/world-placement.js +13 -0
- package/ui/world/world-work-rail.js +7 -1
package/dist/durable-waits.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
1
|
+
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import { defaultJudgmentProblemSnapshotAccessProvider } from './judgment-problem-snapshot.js';
|
|
3
3
|
import { initializePersonalOs, loadPersonalOs, mutatePersonalOsWithSidecar, readPersonalOsSidecar } from './ssot.js';
|
|
4
4
|
/** Story13's persisted contract. The ledger is deliberately separate from the
|
|
@@ -26,7 +26,7 @@ class DurableWaitPreflightConflict extends DurableWaitError {
|
|
|
26
26
|
const SNAPSHOT_ID_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
27
27
|
const DEFAULT_LEASE_MS = 5 * 60 * 1000;
|
|
28
28
|
const WAIT_STATES = [
|
|
29
|
-
'waiting', 'claimed', 'resumed', 'handoff_required', 'reconciliation_wait', 'failed', 'cancelled'
|
|
29
|
+
'waiting', 'claimed', 'resumed', 'handoff_required', 'reconciliation_wait', 'effect_confirmed', 'failed', 'cancelled'
|
|
30
30
|
];
|
|
31
31
|
const RESUME_METHODS = [
|
|
32
32
|
'resume_run', 'new_problem', 'reconcile_external_effect', 'manual_review'
|
|
@@ -35,6 +35,43 @@ const FAILURE_POLICIES = [
|
|
|
35
35
|
'handoff', 'new_problem', 'reconciliation_wait', 'fail'
|
|
36
36
|
];
|
|
37
37
|
const TRIGGERS = ['event', 'timer', 'manual'];
|
|
38
|
+
const DUE_CURSOR_KEY = randomBytes(32);
|
|
39
|
+
function earliestDueAt(deadline) {
|
|
40
|
+
const values = [deadline?.due_at, deadline?.next_review_at].filter((value) => value !== undefined);
|
|
41
|
+
return values.sort((a, b) => Date.parse(a) - Date.parse(b))[0];
|
|
42
|
+
}
|
|
43
|
+
function encodeDueCursor(after, tenantId, principal, scopeType, scopeId) {
|
|
44
|
+
const payload = Buffer.from(JSON.stringify({ v: 1, after, tenantId, principal, scopeType, scopeId }), 'utf8').toString('base64url');
|
|
45
|
+
const signature = createHmac('sha256', DUE_CURSOR_KEY).update(payload).digest('base64url');
|
|
46
|
+
return `${payload}.${signature}`;
|
|
47
|
+
}
|
|
48
|
+
function decodeDueCursor(cursor, tenantId, principal, scopeType, scopeId) {
|
|
49
|
+
if (cursor === undefined)
|
|
50
|
+
return '';
|
|
51
|
+
if (typeof cursor !== 'string' || cursor.length === 0 || cursor.length > 4096 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u.test(cursor)) {
|
|
52
|
+
throw new DurableWaitError('invalid_request', 'cursor is invalid');
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const [payload, signature] = cursor.split('.');
|
|
56
|
+
const expected = createHmac('sha256', DUE_CURSOR_KEY).update(payload).digest();
|
|
57
|
+
const received = Buffer.from(signature, 'base64url');
|
|
58
|
+
if (expected.length !== received.length || !timingSafeEqual(expected, received))
|
|
59
|
+
throw new Error('cursor signature is invalid');
|
|
60
|
+
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
61
|
+
if (decoded === null || typeof decoded !== 'object' || Array.isArray(decoded))
|
|
62
|
+
throw new Error('cursor is not an object');
|
|
63
|
+
const value = decoded;
|
|
64
|
+
if (value.v !== 1 || value.tenantId !== tenantId || value.principal !== principal
|
|
65
|
+
|| value.scopeType !== scopeType || value.scopeId !== scopeId
|
|
66
|
+
|| typeof value.after !== 'string' || value.after.length === 0 || value.after.length > 512) {
|
|
67
|
+
throw new Error('cursor binding is invalid');
|
|
68
|
+
}
|
|
69
|
+
return value.after;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new DurableWaitError('invalid_request', 'cursor is invalid for this principal and scope');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
38
75
|
export const defaultDurableWaitAccessProvider = {
|
|
39
76
|
authorize({ action, principal, acl }) {
|
|
40
77
|
return defaultJudgmentProblemSnapshotAccessProvider.authorize({
|
|
@@ -48,6 +85,8 @@ export class DurableWaitStore {
|
|
|
48
85
|
dataDir;
|
|
49
86
|
problemSnapshot;
|
|
50
87
|
access;
|
|
88
|
+
externalEffect;
|
|
89
|
+
unknownEffect;
|
|
51
90
|
clock;
|
|
52
91
|
sidecarPath;
|
|
53
92
|
defaultLeaseMs;
|
|
@@ -59,6 +98,8 @@ export class DurableWaitStore {
|
|
|
59
98
|
this.dataDir = options.dataDir;
|
|
60
99
|
this.problemSnapshot = options.problemSnapshot;
|
|
61
100
|
this.access = options.access ?? defaultDurableWaitAccessProvider;
|
|
101
|
+
this.externalEffect = options.externalEffect;
|
|
102
|
+
this.unknownEffect = options.unknownEffect;
|
|
62
103
|
this.clock = options.clock ?? (() => new Date());
|
|
63
104
|
this.sidecarPath = options.sidecarPath ?? DURABLE_WAIT_SIDECAR_PATH;
|
|
64
105
|
assertSidecarPath(this.sidecarPath);
|
|
@@ -84,6 +125,7 @@ export class DurableWaitStore {
|
|
|
84
125
|
const record = freezeClone({
|
|
85
126
|
wait_id: normalized.wait_id,
|
|
86
127
|
version: DURABLE_WAIT_VERSION,
|
|
128
|
+
...(normalized.tenant_id === undefined ? {} : { tenant_id: normalized.tenant_id }),
|
|
87
129
|
owner_scope: normalized.owner_scope,
|
|
88
130
|
read_policy: normalized.read_policy,
|
|
89
131
|
problem_snapshot: normalized.problem_snapshot,
|
|
@@ -141,6 +183,56 @@ export class DurableWaitStore {
|
|
|
141
183
|
}
|
|
142
184
|
return result;
|
|
143
185
|
}
|
|
186
|
+
/** Discovery only: claim repeats all live authorization and trigger checks. */
|
|
187
|
+
async listDueCandidates(input) {
|
|
188
|
+
assertText(input?.tenant_id, 'tenant_id');
|
|
189
|
+
assertText(input?.principal, 'principal');
|
|
190
|
+
if (!['personal', 'project', 'organization'].includes(input?.owner_scope_type)) {
|
|
191
|
+
throw new DurableWaitError('invalid_request', 'owner_scope_type is invalid');
|
|
192
|
+
}
|
|
193
|
+
assertText(input?.owner_scope_id, 'owner_scope_id');
|
|
194
|
+
const limit = input.limit ?? 50;
|
|
195
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
|
|
196
|
+
throw new DurableWaitError('invalid_request', 'limit must be an integer from 1 to 100');
|
|
197
|
+
}
|
|
198
|
+
const after = decodeDueCursor(input.cursor, input.tenant_id, input.principal, input.owner_scope_type, input.owner_scope_id);
|
|
199
|
+
const nowMs = this.now().getTime();
|
|
200
|
+
await this.initialize();
|
|
201
|
+
const ledger = parseLedger(await readPersonalOsSidecar(this.dataDir, this.sidecarPath));
|
|
202
|
+
const candidates = [];
|
|
203
|
+
let hasMore = false;
|
|
204
|
+
for (const record of [...ledger.waits].sort((a, b) => a.wait_id < b.wait_id ? -1 : a.wait_id > b.wait_id ? 1 : 0)) {
|
|
205
|
+
if (record.wait_id <= after || record.tenant_id !== input.tenant_id
|
|
206
|
+
|| record.owner_scope.type !== input.owner_scope_type
|
|
207
|
+
|| record.owner_scope.id !== input.owner_scope_id || record.state !== 'waiting')
|
|
208
|
+
continue;
|
|
209
|
+
const dueAt = earliestDueAt(record.deadline);
|
|
210
|
+
if (dueAt === undefined || Date.parse(dueAt) > nowMs)
|
|
211
|
+
continue;
|
|
212
|
+
let allowed;
|
|
213
|
+
try {
|
|
214
|
+
allowed = await this.access.authorize({ action: 'claim', principal: input.principal, acl: record.read_policy, wait: record });
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
throw new DurableWaitError('authorization_provider_failed', 'Claim access could not be verified');
|
|
218
|
+
}
|
|
219
|
+
if (allowed === false)
|
|
220
|
+
continue;
|
|
221
|
+
await this.verifyProblem(input.principal, record.problem_snapshot);
|
|
222
|
+
if (candidates.length === limit) {
|
|
223
|
+
hasMore = true;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
candidates.push({ wait_id: record.wait_id, due_at: dueAt });
|
|
227
|
+
}
|
|
228
|
+
const last = candidates.at(-1);
|
|
229
|
+
return {
|
|
230
|
+
candidates,
|
|
231
|
+
...(hasMore && last !== undefined
|
|
232
|
+
? { next_cursor: encodeDueCursor(last.wait_id, input.tenant_id, input.principal, input.owner_scope_type, input.owner_scope_id) }
|
|
233
|
+
: {})
|
|
234
|
+
};
|
|
235
|
+
}
|
|
144
236
|
async claim(input) {
|
|
145
237
|
const normalized = normalizeClaimInput(input, this.now(), this.defaultLeaseMs);
|
|
146
238
|
await this.initialize();
|
|
@@ -155,9 +247,12 @@ export class DurableWaitStore {
|
|
|
155
247
|
if (record.state === 'reconciliation_wait') {
|
|
156
248
|
throw new DurableWaitError('reconciliation_required', `Wait ${record.wait_id} requires external-effect reconciliation`, record.wait_id);
|
|
157
249
|
}
|
|
158
|
-
if (record.state === 'handoff_required' || record.state === 'resumed' || record.state === 'failed' || record.state === 'cancelled') {
|
|
250
|
+
if (record.state === 'handoff_required' || record.state === 'resumed' || record.state === 'effect_confirmed' || record.state === 'failed' || record.state === 'cancelled') {
|
|
159
251
|
throw new DurableWaitError('state_conflict', `Wait ${record.wait_id} is ${record.state}`, record.wait_id);
|
|
160
252
|
}
|
|
253
|
+
if (record.reconciliation_receipts?.some((receipt) => receipt.previous_claim_request_id === normalized.request_id)) {
|
|
254
|
+
throw new DurableWaitError('state_conflict', 'A reconciled effect requires a fresh claim request_id', record.wait_id);
|
|
255
|
+
}
|
|
161
256
|
// Validate the incoming event/timer before treating an existing claim as
|
|
162
257
|
// a duplicate. A different, not-yet-ready trigger must not receive a
|
|
163
258
|
// claim it cannot legitimately resume.
|
|
@@ -319,7 +414,10 @@ export class DurableWaitStore {
|
|
|
319
414
|
return mutatePersonalOsWithSidecar(this.dataDir, this.sidecarPath, (current, content) => {
|
|
320
415
|
const ledger = this.assertMutationPreflight(current, content, preflight, normalized.wait_id);
|
|
321
416
|
const record = findWait(ledger, normalized.wait_id);
|
|
322
|
-
if (record.state === '
|
|
417
|
+
if (record.state === 'reconciliation_wait') {
|
|
418
|
+
throw new DurableWaitError('reconciliation_required', `Wait ${record.wait_id} requires external-effect reconciliation`, record.wait_id);
|
|
419
|
+
}
|
|
420
|
+
if (record.state === 'resumed' || record.state === 'effect_confirmed' || record.state === 'cancelled' || record.state === 'failed') {
|
|
323
421
|
throw new DurableWaitError('state_conflict', `Wait ${record.wait_id} is ${record.state}`, record.wait_id);
|
|
324
422
|
}
|
|
325
423
|
const now = this.now().toISOString();
|
|
@@ -344,6 +442,67 @@ export class DurableWaitStore {
|
|
|
344
442
|
}
|
|
345
443
|
async markEffectUnknown(input) {
|
|
346
444
|
const normalized = normalizeEffectUnknownInput(input);
|
|
445
|
+
const externalOperationId = normalized.external_operation_id;
|
|
446
|
+
if (externalOperationId === undefined)
|
|
447
|
+
return this.markEffectUnknownUnbound(normalized);
|
|
448
|
+
await this.initialize();
|
|
449
|
+
const preflight = await this.captureMutationPreflight(normalized.wait_id);
|
|
450
|
+
const preflightRecord = requireMutationRecord(preflight, normalized.wait_id);
|
|
451
|
+
await this.authorize('write', normalized.principal, preflightRecord.read_policy, preflightRecord);
|
|
452
|
+
await this.verifyProblem(normalized.principal, preflightRecord.problem_snapshot);
|
|
453
|
+
if ((preflightRecord.state !== 'claimed' && preflightRecord.state !== 'handoff_required') || preflightRecord.claim === undefined) {
|
|
454
|
+
throw new DurableWaitError('state_conflict', `Wait ${normalized.wait_id} has no claim to bind the uncertain effect`, normalized.wait_id);
|
|
455
|
+
}
|
|
456
|
+
let bindingVerification;
|
|
457
|
+
if (this.unknownEffect && typeof this.unknownEffect.verify === 'function') {
|
|
458
|
+
try {
|
|
459
|
+
bindingVerification = await this.unknownEffect.verify({
|
|
460
|
+
principal: normalized.principal,
|
|
461
|
+
wait: freezeClone(preflightRecord),
|
|
462
|
+
claim_id: preflightRecord.claim.claim_id,
|
|
463
|
+
external_operation_id: externalOperationId
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// The effect is still uncertain. Persist a quarantine rather than
|
|
468
|
+
// leaving a claimed lease eligible for handoff and duplicate execution.
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const bindingVerified = !!bindingVerification && bindingVerification.wait_id === normalized.wait_id
|
|
472
|
+
&& bindingVerification.claim_id === preflightRecord.claim.claim_id
|
|
473
|
+
&& bindingVerification.run_id === preflightRecord.run_ref.run_id
|
|
474
|
+
&& bindingVerification.external_operation_id === externalOperationId
|
|
475
|
+
&& bindingVerification.status === 'unknown';
|
|
476
|
+
await this.authorize('write', normalized.principal, preflightRecord.read_policy, preflightRecord);
|
|
477
|
+
return mutatePersonalOsWithSidecar(this.dataDir, this.sidecarPath, (current, content) => {
|
|
478
|
+
const ledger = this.assertMutationPreflight(current, content, preflight, normalized.wait_id);
|
|
479
|
+
const record = findWait(ledger, normalized.wait_id);
|
|
480
|
+
if ((record.state !== 'claimed' && record.state !== 'handoff_required') || record.claim === undefined) {
|
|
481
|
+
throw new DurableWaitError('state_conflict', `Wait ${record.wait_id} has no claim to bind the uncertain effect`, record.wait_id);
|
|
482
|
+
}
|
|
483
|
+
const now = this.now().toISOString();
|
|
484
|
+
const responsible = normalized.responsible ?? record.responsible;
|
|
485
|
+
const updated = updateRecord(record, {
|
|
486
|
+
state: 'reconciliation_wait',
|
|
487
|
+
responsible,
|
|
488
|
+
reconciliation: { reason: normalized.reason, recorded_at: now, responsible, external_operation_id: externalOperationId, claim_id: record.claim.claim_id, binding_status: bindingVerified ? 'verified' : 'unverified' },
|
|
489
|
+
updated_at: now,
|
|
490
|
+
history: appendHistory(record, {
|
|
491
|
+
action: 'effect_unknown',
|
|
492
|
+
occurred_at: now,
|
|
493
|
+
principal: normalized.principal,
|
|
494
|
+
from_state: record.state,
|
|
495
|
+
to_state: 'reconciliation_wait',
|
|
496
|
+
detail: `${normalized.reason}:${externalOperationId}`,
|
|
497
|
+
responsible
|
|
498
|
+
})
|
|
499
|
+
});
|
|
500
|
+
replaceWait(ledger, updated);
|
|
501
|
+
return { next: current, sidecarContent: serializeLedger(ledger), result: freezeClone(updated) };
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
/** Previous behavior, kept for callers that do not send external_operation_id. */
|
|
505
|
+
async markEffectUnknownUnbound(normalized) {
|
|
347
506
|
await this.initialize();
|
|
348
507
|
const preflight = await this.captureMutationPreflight(normalized.wait_id);
|
|
349
508
|
const preflightRecord = requireMutationRecord(preflight, normalized.wait_id);
|
|
@@ -352,7 +511,7 @@ export class DurableWaitStore {
|
|
|
352
511
|
return mutatePersonalOsWithSidecar(this.dataDir, this.sidecarPath, (current, content) => {
|
|
353
512
|
const ledger = this.assertMutationPreflight(current, content, preflight, normalized.wait_id);
|
|
354
513
|
const record = findWait(ledger, normalized.wait_id);
|
|
355
|
-
if (record.state === 'resumed' || record.state === 'cancelled' || record.state === 'failed') {
|
|
514
|
+
if (record.state === 'resumed' || record.state === 'cancelled' || record.state === 'failed' || record.state === 'effect_confirmed') {
|
|
356
515
|
throw new DurableWaitError('state_conflict', `Wait ${record.wait_id} is ${record.state}`, record.wait_id);
|
|
357
516
|
}
|
|
358
517
|
const now = this.now().toISOString();
|
|
@@ -376,6 +535,103 @@ export class DurableWaitStore {
|
|
|
376
535
|
return { next: current, sidecarContent: serializeLedger(ledger), result: freezeClone(updated) };
|
|
377
536
|
});
|
|
378
537
|
}
|
|
538
|
+
async reconcileExternalEffect(input) {
|
|
539
|
+
const normalized = normalizeReconcileExternalEffectInput(input);
|
|
540
|
+
await this.initialize();
|
|
541
|
+
const preflight = await this.captureMutationPreflight(normalized.wait_id);
|
|
542
|
+
const preflightRecord = requireMutationRecord(preflight, normalized.wait_id);
|
|
543
|
+
await this.authorize('write', normalized.principal, preflightRecord.read_policy, preflightRecord);
|
|
544
|
+
await this.verifyProblem(normalized.principal, preflightRecord.problem_snapshot);
|
|
545
|
+
const prior = preflightRecord.reconciliation_receipts?.find((receipt) => receipt.request_id === normalized.request_id);
|
|
546
|
+
if (prior !== undefined) {
|
|
547
|
+
assertReconciliationReplay(prior, normalized);
|
|
548
|
+
return freezeClone(preflightRecord);
|
|
549
|
+
}
|
|
550
|
+
if (preflightRecord.state !== 'reconciliation_wait') {
|
|
551
|
+
throw new DurableWaitError('state_conflict', `Wait ${normalized.wait_id} is ${preflightRecord.state}`, normalized.wait_id);
|
|
552
|
+
}
|
|
553
|
+
const binding = preflightRecord.reconciliation;
|
|
554
|
+
if (!binding?.external_operation_id || !binding.claim_id || binding.binding_status !== 'verified' || !preflightRecord.claim || binding.claim_id !== preflightRecord.claim.claim_id) {
|
|
555
|
+
throw new DurableWaitError('reconciliation_required', 'The uncertain effect has no bound operation and claim identity', normalized.wait_id);
|
|
556
|
+
}
|
|
557
|
+
if (normalized.external_operation_id !== binding.external_operation_id) {
|
|
558
|
+
throw new DurableWaitError('state_conflict', 'Evidence targets a different external operation', normalized.wait_id);
|
|
559
|
+
}
|
|
560
|
+
if (!this.externalEffect || typeof this.externalEffect.verify !== 'function') {
|
|
561
|
+
throw new DurableWaitError('reconciliation_required', 'An authoritative external-effect verifier is required', normalized.wait_id);
|
|
562
|
+
}
|
|
563
|
+
let verification;
|
|
564
|
+
try {
|
|
565
|
+
verification = await this.externalEffect.verify({
|
|
566
|
+
principal: normalized.principal,
|
|
567
|
+
wait: freezeClone(preflightRecord),
|
|
568
|
+
claim_id: binding.claim_id,
|
|
569
|
+
external_operation_id: normalized.external_operation_id,
|
|
570
|
+
evidence_ref: normalized.evidence_ref
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
throw new DurableWaitError('reconciliation_required', 'External-effect evidence could not be verified', normalized.wait_id);
|
|
575
|
+
}
|
|
576
|
+
if (!verification || verification.wait_id !== normalized.wait_id
|
|
577
|
+
|| verification.claim_id !== binding.claim_id
|
|
578
|
+
|| verification.external_operation_id !== binding.external_operation_id
|
|
579
|
+
|| verification.evidence_ref !== normalized.evidence_ref
|
|
580
|
+
|| !['performed', 'not_performed', 'unknown'].includes(verification.outcome)) {
|
|
581
|
+
throw new DurableWaitError('reconciliation_required', 'External-effect verifier returned mismatched evidence', normalized.wait_id);
|
|
582
|
+
}
|
|
583
|
+
const outcome = verification.outcome;
|
|
584
|
+
// The evidence lookup may be slow; do not commit under permission that
|
|
585
|
+
// disappeared while it was running. The ledger CAS covers SSOT changes.
|
|
586
|
+
await this.authorize('write', normalized.principal, preflightRecord.read_policy, preflightRecord);
|
|
587
|
+
try {
|
|
588
|
+
return await mutatePersonalOsWithSidecar(this.dataDir, this.sidecarPath, (current, content) => {
|
|
589
|
+
const ledger = this.assertMutationPreflight(current, content, preflight, normalized.wait_id);
|
|
590
|
+
const record = findWait(ledger, normalized.wait_id);
|
|
591
|
+
if (record.state !== 'reconciliation_wait') {
|
|
592
|
+
throw new DurableWaitError('state_conflict', `Wait ${record.wait_id} is ${record.state}`, record.wait_id);
|
|
593
|
+
}
|
|
594
|
+
const now = this.now().toISOString();
|
|
595
|
+
const receipt = {
|
|
596
|
+
request_id: normalized.request_id,
|
|
597
|
+
external_operation_id: normalized.external_operation_id,
|
|
598
|
+
evidence_ref: normalized.evidence_ref,
|
|
599
|
+
outcome,
|
|
600
|
+
principal: normalized.principal,
|
|
601
|
+
recorded_at: now,
|
|
602
|
+
previous_claim_request_id: record.claim.request_id,
|
|
603
|
+
claim_id: binding.claim_id
|
|
604
|
+
};
|
|
605
|
+
const state = outcome === 'performed' ? 'effect_confirmed' : outcome === 'not_performed' ? 'waiting' : 'reconciliation_wait';
|
|
606
|
+
const updated = updateRecord(record, {
|
|
607
|
+
state,
|
|
608
|
+
updated_at: now,
|
|
609
|
+
reconciliation_receipts: [...(record.reconciliation_receipts ?? []), receipt],
|
|
610
|
+
history: appendHistory(record, {
|
|
611
|
+
action: 'effect_reconciled', occurred_at: now, principal: normalized.principal,
|
|
612
|
+
from_state: 'reconciliation_wait', to_state: state, detail: `${outcome}:${normalized.external_operation_id}`
|
|
613
|
+
})
|
|
614
|
+
});
|
|
615
|
+
// A proven non-effect can be claimed again, but never with the old lease.
|
|
616
|
+
if (outcome === 'not_performed')
|
|
617
|
+
delete updated.claim;
|
|
618
|
+
replaceWait(ledger, updated);
|
|
619
|
+
return { next: current, sidecarContent: serializeLedger(ledger), result: freezeClone(updated) };
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
if (!(error instanceof DurableWaitPreflightConflict))
|
|
624
|
+
throw error;
|
|
625
|
+
const current = await this.readStored(normalized.wait_id);
|
|
626
|
+
await this.authorize('write', normalized.principal, current.read_policy, current);
|
|
627
|
+
await this.verifyProblem(normalized.principal, current.problem_snapshot);
|
|
628
|
+
const winner = current.reconciliation_receipts?.find((receipt) => receipt.request_id === normalized.request_id);
|
|
629
|
+
if (winner === undefined)
|
|
630
|
+
throw error;
|
|
631
|
+
assertReconciliationReplay(winner, normalized);
|
|
632
|
+
return freezeClone(current);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
379
635
|
async captureMutationPreflight(waitId) {
|
|
380
636
|
// These reads intentionally happen outside mutatePersonalOsWithSidecar's
|
|
381
637
|
// lock. A trusted provider may use GraphFoundationRevisionStore or read
|
|
@@ -482,6 +738,8 @@ export function createDurableWaitStore(options) {
|
|
|
482
738
|
}
|
|
483
739
|
function normalizeCreateInput(input, now) {
|
|
484
740
|
assertText(input?.principal, 'principal');
|
|
741
|
+
if (input.tenant_id !== undefined)
|
|
742
|
+
assertText(input.tenant_id, 'tenant_id');
|
|
485
743
|
const waitId = input.wait_id ?? `wait-${randomUUID()}`;
|
|
486
744
|
assertText(waitId, 'wait_id');
|
|
487
745
|
const ownerScope = normalizeScope(input.owner_scope, 'owner_scope');
|
|
@@ -506,6 +764,7 @@ function normalizeCreateInput(input, now) {
|
|
|
506
764
|
assertTimestamp(createdAt, 'created_at');
|
|
507
765
|
return {
|
|
508
766
|
wait_id: waitId,
|
|
767
|
+
...(input.tenant_id === undefined ? {} : { tenant_id: input.tenant_id }),
|
|
509
768
|
principal: input.principal,
|
|
510
769
|
owner_scope: ownerScope,
|
|
511
770
|
read_policy: readPolicy,
|
|
@@ -577,7 +836,28 @@ function normalizeEffectUnknownInput(input) {
|
|
|
577
836
|
const base = normalizeReadInput(input);
|
|
578
837
|
assertText(input.reason, 'reason');
|
|
579
838
|
const responsible = input.responsible === undefined ? undefined : normalizeResponsible(input.responsible, 'responsible');
|
|
580
|
-
return {
|
|
839
|
+
return {
|
|
840
|
+
...base,
|
|
841
|
+
reason: input.reason,
|
|
842
|
+
...(input.external_operation_id === undefined ? {} : { external_operation_id: boundedText(input.external_operation_id, 'external_operation_id') }),
|
|
843
|
+
...(responsible === undefined ? {} : { responsible })
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
function normalizeReconcileExternalEffectInput(input) {
|
|
847
|
+
const base = normalizeReadInput(input);
|
|
848
|
+
return {
|
|
849
|
+
...base,
|
|
850
|
+
request_id: boundedText(input.request_id, 'request_id'),
|
|
851
|
+
external_operation_id: boundedText(input.external_operation_id, 'external_operation_id'),
|
|
852
|
+
evidence_ref: boundedText(input.evidence_ref, 'evidence_ref')
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function assertReconciliationReplay(receipt, input) {
|
|
856
|
+
if (receipt.principal !== input.principal
|
|
857
|
+
|| receipt.external_operation_id !== input.external_operation_id
|
|
858
|
+
|| receipt.evidence_ref !== input.evidence_ref) {
|
|
859
|
+
throw new DurableWaitError('state_conflict', 'Reconciliation request_id was already used with different evidence', input.wait_id);
|
|
860
|
+
}
|
|
581
861
|
}
|
|
582
862
|
function normalizeCondition(value) {
|
|
583
863
|
if (value === undefined || value === null || typeof value !== 'object')
|
|
@@ -755,6 +1035,8 @@ function validateStoredRecord(value) {
|
|
|
755
1035
|
if (value.version !== DURABLE_WAIT_VERSION)
|
|
756
1036
|
throw new Error('wait.version is unsupported');
|
|
757
1037
|
assertText(value.wait_id, 'wait.wait_id');
|
|
1038
|
+
if (value.tenant_id !== undefined)
|
|
1039
|
+
assertText(value.tenant_id, 'wait.tenant_id');
|
|
758
1040
|
const ownerScope = normalizeScope(value.owner_scope, 'wait.owner_scope');
|
|
759
1041
|
const acl = normalizeAcl(value.read_policy, 'wait.read_policy');
|
|
760
1042
|
const problem = normalizeProblemReference(value.problem_snapshot, 'wait.problem_snapshot');
|
|
@@ -773,9 +1055,11 @@ function validateStoredRecord(value) {
|
|
|
773
1055
|
const resumeReceipt = value.resume_receipt === undefined ? undefined : validateResumeReceipt(value.resume_receipt);
|
|
774
1056
|
const nextProblem = value.next_problem === undefined ? undefined : normalizeProblemReference(value.next_problem, 'wait.next_problem');
|
|
775
1057
|
const reconciliation = value.reconciliation === undefined ? undefined : validateReconciliation(value.reconciliation);
|
|
1058
|
+
const reconciliationReceipts = value.reconciliation_receipts === undefined ? undefined : validateReconciliationReceipts(value.reconciliation_receipts);
|
|
776
1059
|
return {
|
|
777
1060
|
wait_id: value.wait_id,
|
|
778
1061
|
version: DURABLE_WAIT_VERSION,
|
|
1062
|
+
...(value.tenant_id === undefined ? {} : { tenant_id: value.tenant_id }),
|
|
779
1063
|
owner_scope: ownerScope,
|
|
780
1064
|
read_policy: acl,
|
|
781
1065
|
problem_snapshot: problem,
|
|
@@ -792,6 +1076,7 @@ function validateStoredRecord(value) {
|
|
|
792
1076
|
...(resumeReceipt === undefined ? {} : { resume_receipt: resumeReceipt }),
|
|
793
1077
|
...(nextProblem === undefined ? {} : { next_problem: nextProblem }),
|
|
794
1078
|
...(reconciliation === undefined ? {} : { reconciliation }),
|
|
1079
|
+
...(reconciliationReceipts === undefined ? {} : { reconciliation_receipts: reconciliationReceipts }),
|
|
795
1080
|
history
|
|
796
1081
|
};
|
|
797
1082
|
}
|
|
@@ -846,7 +1131,7 @@ function validateResumeReceipt(value) {
|
|
|
846
1131
|
function validateHistory(value) {
|
|
847
1132
|
if (!isRecord(value))
|
|
848
1133
|
throw new Error('wait.history entry must be an object');
|
|
849
|
-
const action = enumValue(value.action, ['create', 'claim', 'handoff', 'premise_changed', 'effect_unknown', 'resume'], 'wait.history.action');
|
|
1134
|
+
const action = enumValue(value.action, ['create', 'claim', 'handoff', 'premise_changed', 'effect_unknown', 'effect_reconciled', 'resume'], 'wait.history.action');
|
|
850
1135
|
assertTimestamp(value.occurred_at, 'wait.history.occurred_at');
|
|
851
1136
|
assertText(value.principal, 'wait.history.principal');
|
|
852
1137
|
const toState = enumValue(value.to_state, WAIT_STATES, 'wait.history.to_state');
|
|
@@ -862,7 +1147,34 @@ function validateReconciliation(value) {
|
|
|
862
1147
|
const recordedAt = boundedText(value.recorded_at, 'wait.reconciliation.recorded_at');
|
|
863
1148
|
assertTimestamp(recordedAt, 'wait.reconciliation.recorded_at');
|
|
864
1149
|
const responsible = normalizeResponsible(value.responsible, 'wait.reconciliation.responsible');
|
|
865
|
-
|
|
1150
|
+
const externalOperationId = value.external_operation_id === undefined ? undefined : boundedText(value.external_operation_id, 'wait.reconciliation.external_operation_id');
|
|
1151
|
+
const claimId = value.claim_id === undefined ? undefined : boundedText(value.claim_id, 'wait.reconciliation.claim_id');
|
|
1152
|
+
const bindingStatus = value.binding_status === undefined ? undefined : enumValue(value.binding_status, ['verified', 'unverified'], 'wait.reconciliation.binding_status');
|
|
1153
|
+
if ((externalOperationId === undefined) !== (claimId === undefined))
|
|
1154
|
+
throw new Error('wait.reconciliation binding is incomplete');
|
|
1155
|
+
if (bindingStatus !== undefined && externalOperationId === undefined)
|
|
1156
|
+
throw new Error('wait.reconciliation binding status has no operation');
|
|
1157
|
+
return { reason, recorded_at: recordedAt, responsible, ...(externalOperationId === undefined ? {} : { external_operation_id: externalOperationId, claim_id: claimId }), ...(bindingStatus === undefined ? {} : { binding_status: bindingStatus }) };
|
|
1158
|
+
}
|
|
1159
|
+
function validateReconciliationReceipts(value) {
|
|
1160
|
+
if (!Array.isArray(value))
|
|
1161
|
+
throw new Error('wait.reconciliation_receipts must be an array');
|
|
1162
|
+
const receipts = value.map((item) => {
|
|
1163
|
+
if (!isRecord(item))
|
|
1164
|
+
throw new Error('wait.reconciliation_receipt must be an object');
|
|
1165
|
+
const requestId = boundedText(item.request_id, 'wait.reconciliation_receipt.request_id');
|
|
1166
|
+
const externalOperationId = boundedText(item.external_operation_id, 'wait.reconciliation_receipt.external_operation_id');
|
|
1167
|
+
const evidenceRef = boundedText(item.evidence_ref, 'wait.reconciliation_receipt.evidence_ref');
|
|
1168
|
+
const outcome = enumValue(item.outcome, ['performed', 'not_performed', 'unknown'], 'wait.reconciliation_receipt.outcome');
|
|
1169
|
+
const principal = boundedText(item.principal, 'wait.reconciliation_receipt.principal');
|
|
1170
|
+
assertTimestamp(item.recorded_at, 'wait.reconciliation_receipt.recorded_at');
|
|
1171
|
+
const previousClaimRequestId = item.previous_claim_request_id === undefined ? undefined : boundedText(item.previous_claim_request_id, 'wait.reconciliation_receipt.previous_claim_request_id');
|
|
1172
|
+
const claimId = item.claim_id === undefined ? undefined : boundedText(item.claim_id, 'wait.reconciliation_receipt.claim_id');
|
|
1173
|
+
return { request_id: requestId, external_operation_id: externalOperationId, evidence_ref: evidenceRef, outcome, principal, recorded_at: item.recorded_at, ...(previousClaimRequestId === undefined ? {} : { previous_claim_request_id: previousClaimRequestId }), ...(claimId === undefined ? {} : { claim_id: claimId }) };
|
|
1174
|
+
});
|
|
1175
|
+
if (new Set(receipts.map((receipt) => receipt.request_id)).size !== receipts.length)
|
|
1176
|
+
throw new Error('duplicate reconciliation request_id');
|
|
1177
|
+
return receipts;
|
|
866
1178
|
}
|
|
867
1179
|
function serializeLedger(ledger) {
|
|
868
1180
|
const sorted = {
|
package/dist/honcho.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Portable Honcho v3 HTTP boundary. Credentials and identity are host ports. */
|
|
2
|
+
export declare const HONCHO_CONTRACT_VERSION: "honcho.v1";
|
|
3
|
+
export interface HonchoConfig {
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface HonchoRequest {
|
|
8
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
9
|
+
path: string;
|
|
10
|
+
query?: Record<string, string | number | boolean>;
|
|
11
|
+
body?: Record<string, unknown> | unknown[] | null;
|
|
12
|
+
}
|
|
13
|
+
export declare class HonchoContractError extends Error {
|
|
14
|
+
readonly code = "honcho_contract_invalid";
|
|
15
|
+
constructor(field: string, message?: string);
|
|
16
|
+
}
|
|
17
|
+
export declare function normalizeHonchoConfig(value: unknown): HonchoConfig;
|
|
18
|
+
export declare function honchoWorkspacePath(workspaceId: string): string;
|
|
19
|
+
export declare function createHonchoRequest(input: HonchoRequest): HonchoRequest;
|
|
20
|
+
export declare function honchoRequestUrl(config: HonchoConfig, request: HonchoRequest): string;
|
|
21
|
+
export interface HonchoMessageInput {
|
|
22
|
+
peer: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function normalizeHonchoMessages(value: unknown): HonchoMessageInput[];
|
package/dist/honcho.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Portable Honcho v3 HTTP boundary. Credentials and identity are host ports. */
|
|
2
|
+
export const HONCHO_CONTRACT_VERSION = 'honcho.v1';
|
|
3
|
+
export class HonchoContractError extends Error {
|
|
4
|
+
code = 'honcho_contract_invalid';
|
|
5
|
+
constructor(field, message = `Honcho field is invalid: ${field}`) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'HonchoContractError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function requiredText(value, field) {
|
|
11
|
+
if (typeof value !== 'string' || !value.trim())
|
|
12
|
+
throw new HonchoContractError(field);
|
|
13
|
+
return value.trim();
|
|
14
|
+
}
|
|
15
|
+
export function normalizeHonchoConfig(value) {
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
17
|
+
throw new HonchoContractError('config');
|
|
18
|
+
const candidate = value;
|
|
19
|
+
const rawUrl = requiredText(candidate.apiUrl, 'apiUrl');
|
|
20
|
+
let url;
|
|
21
|
+
try {
|
|
22
|
+
url = new URL(rawUrl);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new HonchoContractError('apiUrl', 'apiUrl must be an absolute URL');
|
|
26
|
+
}
|
|
27
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
28
|
+
throw new HonchoContractError('apiUrl', 'apiUrl must use http or https');
|
|
29
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
30
|
+
throw new HonchoContractError('apiUrl', 'apiUrl must not contain credentials, query, or hash components');
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
apiUrl: url.toString().replace(/\/+$/u, ''),
|
|
34
|
+
workspaceId: requiredText(candidate.workspaceId ?? 'brainbase', 'workspaceId')
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function honchoWorkspacePath(workspaceId) {
|
|
38
|
+
return `/v3/workspaces/${encodeURIComponent(requiredText(workspaceId, 'workspaceId'))}`;
|
|
39
|
+
}
|
|
40
|
+
export function createHonchoRequest(input) {
|
|
41
|
+
if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(input.method)) {
|
|
42
|
+
throw new HonchoContractError('method');
|
|
43
|
+
}
|
|
44
|
+
const path = requiredText(input.path, 'path');
|
|
45
|
+
if (!path.startsWith('/') || path.includes('://') || path.includes('\n') || path.includes('\r')) {
|
|
46
|
+
throw new HonchoContractError('path');
|
|
47
|
+
}
|
|
48
|
+
if (input.query) {
|
|
49
|
+
for (const [key, value] of Object.entries(input.query)) {
|
|
50
|
+
if (!key || value === undefined || value === null)
|
|
51
|
+
throw new HonchoContractError('query');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
method: input.method,
|
|
56
|
+
path,
|
|
57
|
+
query: input.query ? { ...input.query } : undefined,
|
|
58
|
+
body: input.body === undefined ? undefined : input.body === null ? null : structuredClone(input.body)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function honchoRequestUrl(config, request) {
|
|
62
|
+
const normalized = normalizeHonchoConfig(config);
|
|
63
|
+
const safeRequest = createHonchoRequest(request);
|
|
64
|
+
const url = new URL(`${normalized.apiUrl}${safeRequest.path}`);
|
|
65
|
+
for (const [key, value] of Object.entries(safeRequest.query ?? {}))
|
|
66
|
+
url.searchParams.set(key, String(value));
|
|
67
|
+
return url.toString();
|
|
68
|
+
}
|
|
69
|
+
export function normalizeHonchoMessages(value) {
|
|
70
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
71
|
+
throw new HonchoContractError('messages');
|
|
72
|
+
return value.map((item, index) => {
|
|
73
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
74
|
+
throw new HonchoContractError(`messages.${index}`);
|
|
75
|
+
const candidate = item;
|
|
76
|
+
return {
|
|
77
|
+
peer: requiredText(candidate.peer, `messages.${index}.peer`),
|
|
78
|
+
content: requiredText(candidate.content, `messages.${index}.content`)
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type JudgmentExecutionOutcome } from './judgment-execution-outcome.js';
|
|
2
|
+
/** Input boundary for a completed Host episode. Private runtime fields are ignored. */
|
|
3
|
+
export interface JudgmentEpisode {
|
|
4
|
+
episode_id?: unknown;
|
|
5
|
+
session_id?: unknown;
|
|
6
|
+
turn_id?: unknown;
|
|
7
|
+
finalized_at?: unknown;
|
|
8
|
+
answer_digest?: unknown;
|
|
9
|
+
completion_status?: unknown;
|
|
10
|
+
final_summary?: unknown;
|
|
11
|
+
final_answer?: unknown;
|
|
12
|
+
summary?: unknown;
|
|
13
|
+
payload?: unknown;
|
|
14
|
+
redaction_status?: unknown;
|
|
15
|
+
execution_outcome?: unknown;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface JudgmentKnowledgeEventPayload {
|
|
19
|
+
summary?: string;
|
|
20
|
+
redaction_status?: 'needs_redaction';
|
|
21
|
+
execution_outcome?: JudgmentExecutionOutcome;
|
|
22
|
+
}
|
|
23
|
+
export interface JudgmentKnowledgeEvent {
|
|
24
|
+
schema_version: 'knowledge_event.v1';
|
|
25
|
+
contract_version: 'knowledge_event.v1';
|
|
26
|
+
event_id: string;
|
|
27
|
+
occurred_at: string;
|
|
28
|
+
captured_at: string;
|
|
29
|
+
source: {
|
|
30
|
+
type: 'agent_judgment' | 'codex_judgment';
|
|
31
|
+
ref: string;
|
|
32
|
+
};
|
|
33
|
+
subject: {
|
|
34
|
+
type: 'judgment_episode';
|
|
35
|
+
id: string;
|
|
36
|
+
};
|
|
37
|
+
decision_authority: {
|
|
38
|
+
kind: 'judgment_receipt';
|
|
39
|
+
authorized: false;
|
|
40
|
+
graph_promotion_allowed: false;
|
|
41
|
+
};
|
|
42
|
+
applicability_scope: {
|
|
43
|
+
scope: 'judgment_episode';
|
|
44
|
+
project_code: 'brainbase';
|
|
45
|
+
};
|
|
46
|
+
permission_snapshot: {
|
|
47
|
+
knowledge_registration: true;
|
|
48
|
+
external_action: false;
|
|
49
|
+
};
|
|
50
|
+
source_pointer: {
|
|
51
|
+
uri: string;
|
|
52
|
+
};
|
|
53
|
+
body_hash: string;
|
|
54
|
+
parent_episode_id: string;
|
|
55
|
+
semantic_state?: 'quarantined';
|
|
56
|
+
payload: JudgmentKnowledgeEventPayload;
|
|
57
|
+
}
|
|
58
|
+
/** Remove audit lines and quarantine likely secrets from a judgment answer. */
|
|
59
|
+
export declare function sanitizeJudgmentAnswer(value: unknown): {
|
|
60
|
+
sensitive: true;
|
|
61
|
+
redaction_status: 'needs_redaction';
|
|
62
|
+
} | {
|
|
63
|
+
sensitive: false;
|
|
64
|
+
summary: string;
|
|
65
|
+
} | null;
|
|
66
|
+
/**
|
|
67
|
+
* Project a completed Host episode into a non-authoritative knowledge event.
|
|
68
|
+
*
|
|
69
|
+
* This is a portable projection only. Organization identity, Task binding,
|
|
70
|
+
* transcript storage, and delivery/outbox behavior remain adapter concerns.
|
|
71
|
+
*/
|
|
72
|
+
export declare function toKnowledgeEventFromJudgmentEpisode(episode: JudgmentEpisode | null | undefined): JudgmentKnowledgeEvent | null;
|