@jungjaehoon/mama-os 0.48.3 → 0.49.0
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/dist/agent/agent-loop.d.ts +6 -7
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +93 -79
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
- package/dist/agent/code-act/host-bridge.js +2 -10
- package/dist/agent/code-act/host-bridge.js.map +1 -1
- package/dist/agent/codex-home.d.ts.map +1 -1
- package/dist/agent/codex-home.js +3 -1
- package/dist/agent/codex-home.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts +0 -16
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +0 -51
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/gateway-tools.md +1 -2
- package/dist/agent/os-agent-capabilities.md +14 -34
- package/dist/agent/role-manager.d.ts.map +1 -1
- package/dist/agent/role-manager.js +6 -3
- package/dist/agent/role-manager.js.map +1 -1
- package/dist/agent/tool-registry.d.ts.map +1 -1
- package/dist/agent/tool-registry.js +2 -8
- package/dist/agent/tool-registry.js.map +1 -1
- package/dist/agent/types.d.ts +18 -6
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/graph-api-types.d.ts +0 -18
- package/dist/api/graph-api-types.d.ts.map +1 -1
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +9 -81
- package/dist/api/graph-api.js.map +1 -1
- package/dist/cli/commands/start.d.ts +7 -16
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +181 -188
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/config/types.d.ts.map +1 -1
- package/dist/cli/config/types.js +11 -4
- package/dist/cli/config/types.js.map +1 -1
- package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
- package/dist/cli/runtime/agent-loop-init.js +28 -11
- package/dist/cli/runtime/agent-loop-init.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +0 -2
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +9 -95
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/cli/runtime/api-server-init.d.ts +2 -2
- package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-server-init.js +2 -2
- package/dist/cli/runtime/api-server-init.js.map +1 -1
- package/dist/cli/runtime/scheduler-init.d.ts +3 -4
- package/dist/cli/runtime/scheduler-init.d.ts.map +1 -1
- package/dist/cli/runtime/scheduler-init.js +6 -16
- package/dist/cli/runtime/scheduler-init.js.map +1 -1
- package/dist/cli/runtime/shutdown.d.ts +0 -1
- package/dist/cli/runtime/shutdown.d.ts.map +1 -1
- package/dist/cli/runtime/shutdown.js +1 -1
- package/dist/cli/runtime/shutdown.js.map +1 -1
- package/dist/cli/runtime/types.d.ts +0 -5
- package/dist/cli/runtime/types.d.ts.map +1 -1
- package/dist/concurrency/lane-manager.d.ts +1 -1
- package/dist/concurrency/lane-manager.d.ts.map +1 -1
- package/dist/concurrency/lane-manager.js +4 -2
- package/dist/concurrency/lane-manager.js.map +1 -1
- package/dist/concurrency/types.d.ts +4 -0
- package/dist/concurrency/types.d.ts.map +1 -1
- package/dist/concurrency/types.js.map +1 -1
- package/dist/envelope/types.d.ts +1 -1
- package/dist/envelope/types.d.ts.map +1 -1
- package/dist/envelope/types.js.map +1 -1
- package/dist/gateways/message-router.d.ts +13 -71
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +99 -489
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/session-store.d.ts +3 -23
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +2 -64
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/telegram-report-context-store.d.ts +4 -66
- package/dist/gateways/telegram-report-context-store.d.ts.map +1 -1
- package/dist/gateways/telegram-report-context-store.js +11 -235
- package/dist/gateways/telegram-report-context-store.js.map +1 -1
- package/dist/gateways/turn-contract.d.ts +7 -3
- package/dist/gateways/turn-contract.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.d.ts +1 -12
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +6 -40
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/owner-event-loop.d.ts +8 -7
- package/dist/operator/owner-event-loop.d.ts.map +1 -1
- package/dist/operator/owner-event-loop.js +7 -20
- package/dist/operator/owner-event-loop.js.map +1 -1
- package/dist/operator/owner-event-policy.d.ts.map +1 -1
- package/dist/operator/owner-event-policy.js +0 -2
- package/dist/operator/owner-event-policy.js.map +1 -1
- package/dist/operator/owner-event-prompt.d.ts +0 -2
- package/dist/operator/owner-event-prompt.d.ts.map +1 -1
- package/dist/operator/owner-event-prompt.js +1 -8
- package/dist/operator/owner-event-prompt.js.map +1 -1
- package/dist/operator/owner-runtime-journal.d.ts +21 -0
- package/dist/operator/owner-runtime-journal.d.ts.map +1 -0
- package/dist/operator/owner-runtime-journal.js +131 -0
- package/dist/operator/owner-runtime-journal.js.map +1 -0
- package/dist/operator/owner-runtime.d.ts +32 -0
- package/dist/operator/owner-runtime.d.ts.map +1 -0
- package/dist/operator/owner-runtime.js +31 -0
- package/dist/operator/owner-runtime.js.map +1 -0
- package/dist/operator/pending-report-store.d.ts +2 -2
- package/dist/operator/pending-report-store.d.ts.map +1 -1
- package/dist/operator/pending-report-store.js +4 -1
- package/dist/operator/pending-report-store.js.map +1 -1
- package/dist/operator/report-run.d.ts +7 -10
- package/dist/operator/report-run.d.ts.map +1 -1
- package/dist/operator/report-run.js +5 -26
- package/dist/operator/report-run.js.map +1 -1
- package/dist/operator/situation-report.d.ts +25 -6
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +25 -37
- package/dist/operator/situation-report.js.map +1 -1
- package/dist/operator/trigger-author.d.ts +0 -45
- package/dist/operator/trigger-author.d.ts.map +1 -1
- package/dist/operator/trigger-author.js +0 -122
- package/dist/operator/trigger-author.js.map +1 -1
- package/dist/operator/worker-run.d.ts +9 -23
- package/dist/operator/worker-run.d.ts.map +1 -1
- package/dist/operator/worker-run.js +18 -74
- package/dist/operator/worker-run.js.map +1 -1
- package/dist/operator/workorder-consumer.d.ts.map +1 -1
- package/dist/operator/workorder-consumer.js +7 -0
- package/dist/operator/workorder-consumer.js.map +1 -1
- package/dist/scheduler/cron-worker.d.ts +0 -3
- package/dist/scheduler/cron-worker.d.ts.map +1 -1
- package/dist/scheduler/cron-worker.js +2 -13
- package/dist/scheduler/cron-worker.js.map +1 -1
- package/dist/scheduler/heartbeat.d.ts +3 -1
- package/dist/scheduler/heartbeat.d.ts.map +1 -1
- package/dist/scheduler/heartbeat.js +13 -4
- package/dist/scheduler/heartbeat.js.map +1 -1
- package/dist/validation/types.d.ts +1 -1
- package/dist/validation/types.js +1 -1
- package/dist/wiki/wiki-turn-contract.d.ts.map +1 -1
- package/dist/wiki/wiki-turn-contract.js +1 -0
- package/dist/wiki/wiki-turn-contract.js.map +1 -1
- package/dist/wiki-artifacts/wiki-publish-adapter.d.ts.map +1 -1
- package/dist/wiki-artifacts/wiki-publish-adapter.js +11 -1
- package/dist/wiki-artifacts/wiki-publish-adapter.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/runtime/memory-agent-init.d.ts +0 -37
- package/dist/cli/runtime/memory-agent-init.d.ts.map +0 -1
- package/dist/cli/runtime/memory-agent-init.js +0 -202
- package/dist/cli/runtime/memory-agent-init.js.map +0 -1
- package/dist/gateways/owner-report-inbox.d.ts +0 -40
- package/dist/gateways/owner-report-inbox.d.ts.map +0 -1
- package/dist/gateways/owner-report-inbox.js +0 -128
- package/dist/gateways/owner-report-inbox.js.map +0 -1
- package/dist/multi-agent/memory-agent-persona.d.ts +0 -11
- package/dist/multi-agent/memory-agent-persona.d.ts.map +0 -1
- package/dist/multi-agent/memory-agent-persona.js +0 -175
- package/dist/multi-agent/memory-agent-persona.js.map +0 -1
- package/dist/operator/report-context.d.ts +0 -184
- package/dist/operator/report-context.d.ts.map +0 -1
- package/dist/operator/report-context.js +0 -1052
- package/dist/operator/report-context.js.map +0 -1
|
@@ -1,1052 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MAX_OPERATIONAL_ISSUES = void 0;
|
|
4
|
-
exports.serializeOwnerReportContext = serializeOwnerReportContext;
|
|
5
|
-
exports.compileOwnerReportContext = compileOwnerReportContext;
|
|
6
|
-
exports.compileChannelPacket = compileChannelPacket;
|
|
7
|
-
const tool_registry_js_1 = require("../agent/tool-registry.js");
|
|
8
|
-
const MAX_CLAIMS = 30;
|
|
9
|
-
const TASK_PAGE_SIZE = 20;
|
|
10
|
-
const MAX_TRELLO_CARDS_PER_LIST = 100;
|
|
11
|
-
const MAX_TRELLO_CARDS = 300;
|
|
12
|
-
const MAX_CHANGES = 100;
|
|
13
|
-
// One prompt's context window is the only remaining bound on packet size (~128K tokens).
|
|
14
|
-
// 96 KB silently trimmed the task list back to a slice after the task cap was lifted, and
|
|
15
|
-
// the removers below emptied the packet's Trello half first (review of #258, 2026-09-04).
|
|
16
|
-
// Today's board: 367 rows ~ 140 KB. The removers stay as a last resort with this cause.
|
|
17
|
-
// Reachability at this ceiling: only the uncapped task list can exceed it; with claims,
|
|
18
|
-
// trello and changes capped, the largest otherwise-valid packet is ~524,225 bytes, so the
|
|
19
|
-
// correlation and window-evidence removers below are last-resort code that does not run.
|
|
20
|
-
const MAX_PACKET_BYTES = 512 * 1024;
|
|
21
|
-
const REGISTERED_TOOL_NAMES = new Set(tool_registry_js_1.ToolRegistry.getValidToolNames());
|
|
22
|
-
const SOURCE_DISPLAY_LABELS = {
|
|
23
|
-
'claude-code': 'claude-code',
|
|
24
|
-
calendar: 'calendar',
|
|
25
|
-
chatwork: 'chatwork',
|
|
26
|
-
discord: 'discord',
|
|
27
|
-
drive: 'drive',
|
|
28
|
-
gmail: 'gmail',
|
|
29
|
-
imessage: 'imessage',
|
|
30
|
-
kagemusha: 'kagemusha',
|
|
31
|
-
notion: 'notion',
|
|
32
|
-
obsidian: 'obsidian',
|
|
33
|
-
sheets: 'sheets',
|
|
34
|
-
slack: 'slack',
|
|
35
|
-
telegram: 'telegram',
|
|
36
|
-
trello: 'trello',
|
|
37
|
-
};
|
|
38
|
-
function isRecord(value) {
|
|
39
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
40
|
-
}
|
|
41
|
-
function isFiniteDate(value) {
|
|
42
|
-
return value.trim().length > 0 && Number.isFinite(Date.parse(value));
|
|
43
|
-
}
|
|
44
|
-
function isSafeCount(value) {
|
|
45
|
-
return Number.isSafeInteger(value) && value >= 0;
|
|
46
|
-
}
|
|
47
|
-
function validScopeRef(value) {
|
|
48
|
-
if (!isRecord(value) || typeof value.kind !== 'string' || typeof value.id !== 'string') {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
return value.id.trim().length > 0;
|
|
52
|
-
}
|
|
53
|
-
function assertInput(input) {
|
|
54
|
-
const scope = input.readScope;
|
|
55
|
-
const window = input.windowEvidence;
|
|
56
|
-
const scopeValid = isRecord(scope) &&
|
|
57
|
-
Array.isArray(scope.projectRefs) &&
|
|
58
|
-
scope.projectRefs.every((ref) => isRecord(ref) && ref.kind === 'project' && typeof ref.id === 'string' && ref.id !== '') &&
|
|
59
|
-
Array.isArray(scope.memoryScopes) &&
|
|
60
|
-
scope.memoryScopes.every(validScopeRef) &&
|
|
61
|
-
Array.isArray(scope.rawConnectors) &&
|
|
62
|
-
scope.rawConnectors.every((connector) => typeof connector === 'string' && connector !== '');
|
|
63
|
-
const windowValid = isRecord(window) &&
|
|
64
|
-
typeof window.start === 'string' &&
|
|
65
|
-
isFiniteDate(window.start) &&
|
|
66
|
-
typeof window.end === 'string' &&
|
|
67
|
-
isFiniteDate(window.end) &&
|
|
68
|
-
isSafeCount(window.channelCount) &&
|
|
69
|
-
isSafeCount(window.messageCount) &&
|
|
70
|
-
Array.isArray(window.channels) &&
|
|
71
|
-
window.channels.length <= 48 &&
|
|
72
|
-
window.channels.every((channel) => isRecord(channel) &&
|
|
73
|
-
typeof channel.label === 'string' &&
|
|
74
|
-
isSafeCount(channel.count) &&
|
|
75
|
-
Array.isArray(channel.excerpts) &&
|
|
76
|
-
channel.excerpts.length <= 5 &&
|
|
77
|
-
channel.excerpts.every((excerpt) => isRecord(excerpt) &&
|
|
78
|
-
typeof excerpt.authorLabel === 'string' &&
|
|
79
|
-
typeof excerpt.text === 'string' &&
|
|
80
|
-
(excerpt.observedAt === null ||
|
|
81
|
-
(typeof excerpt.observedAt === 'string' && isFiniteDate(excerpt.observedAt))))) &&
|
|
82
|
-
Array.isArray(window.triggerActivity) &&
|
|
83
|
-
window.triggerActivity.length <= 100 &&
|
|
84
|
-
window.triggerActivity.every((activity) => isRecord(activity) &&
|
|
85
|
-
typeof activity.kind === 'string' &&
|
|
86
|
-
isSafeCount(activity.count) &&
|
|
87
|
-
Array.isArray(activity.topics) &&
|
|
88
|
-
activity.topics.every((topic) => typeof topic === 'string'));
|
|
89
|
-
if (!scopeValid ||
|
|
90
|
-
!windowValid ||
|
|
91
|
-
typeof input.since !== 'string' ||
|
|
92
|
-
!isFiniteDate(input.since)) {
|
|
93
|
-
throw new Error('Invalid owner report context input');
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function detachScope(scope) {
|
|
97
|
-
return {
|
|
98
|
-
projectRefs: scope.projectRefs.map((ref) => ({ kind: 'project', id: ref.id })),
|
|
99
|
-
memoryScopes: scope.memoryScopes.map((ref) => ({ kind: ref.kind, id: ref.id })),
|
|
100
|
-
rawConnectors: [...new Set(scope.rawConnectors)].sort(),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function isRegisteredJsonToolObject(value) {
|
|
104
|
-
let parsed;
|
|
105
|
-
try {
|
|
106
|
-
parsed = JSON.parse(value);
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
return (isRecord(parsed) &&
|
|
112
|
-
typeof parsed.name === 'string' &&
|
|
113
|
-
REGISTERED_TOOL_NAMES.has(parsed.name) &&
|
|
114
|
-
(isRecord(parsed.input) || isRecord(parsed.arguments)));
|
|
115
|
-
}
|
|
116
|
-
function hasRegisteredImperativeTarget(value) {
|
|
117
|
-
const matches = value.matchAll(/\b(?:call|use|invoke|run)\s+([A-Za-z][A-Za-z0-9_-]*)\b/gi);
|
|
118
|
-
return [...matches].some((match) => REGISTERED_TOOL_NAMES.has(match[1]));
|
|
119
|
-
}
|
|
120
|
-
function redactText(value, maxLength) {
|
|
121
|
-
if (/^\s*(?:system|developer|assistant)\s*:/i.test(value) ||
|
|
122
|
-
/<\/?(?:tool_call|function_call|invoke)\b/i.test(value) ||
|
|
123
|
-
isRegisteredJsonToolObject(value) ||
|
|
124
|
-
/\bmcp__[a-z0-9_.-]+/i.test(value) ||
|
|
125
|
-
/\b(?:ignore|disregard)\s+(?:all\s+)?(?:prior|previous|above)\s+(?:instructions|messages)\b/i.test(value) ||
|
|
126
|
-
hasRegisteredImperativeTarget(value)) {
|
|
127
|
-
return '[redacted-instruction]'.slice(0, maxLength);
|
|
128
|
-
}
|
|
129
|
-
return (value
|
|
130
|
-
.replace(/```\s*(?:tool_call|json\s*tool_call)[\s\S]*?```/gi, '[redacted-instruction]')
|
|
131
|
-
.replace(/```\s*(?:tool_call|json\s*tool_call)\b/gi, '[redacted-instruction]')
|
|
132
|
-
.replace(/\bignore\s+(?:all\s+)?previous\s+instructions\b/gi, '[redacted-instruction]')
|
|
133
|
-
// Real-data verification (2026-09-02): Board-written latest_event prose and
|
|
134
|
-
// curated claim summaries carried raw event ids (`source_event_id=evt_...`),
|
|
135
|
-
// run ids (`mr_...`) and numeric chat-room ids (`channel=chatwork:123...`) into
|
|
136
|
-
// the model-visible packet. The packet contract forbids raw run/event/source
|
|
137
|
-
// and chat ids; the words around them stay.
|
|
138
|
-
.replace(/\bsource_event_id\s*=\s*\S+/gi, 'source_event_id=[redacted-id]')
|
|
139
|
-
.replace(/\b(?:evt|mr)_[0-9a-f]{8,}\b/gi, '[redacted-id]')
|
|
140
|
-
.replace(/\b(chatwork|telegram|slack|kakao|kagemusha|discord|line)\s*[:=]\s*\d{5,}\b/gi, '$1:[redacted-id]')
|
|
141
|
-
.replace(/(?:~|\/(?:Users|home|private|var|tmp)\/)[^\s,;)}\]]+/g, '[redacted-path]')
|
|
142
|
-
.replace(/\b(token|api[_-]?key|password|secret|authorization)\s*[:=]\s*[^\s,;]+/gi, '$1=[redacted-secret]')
|
|
143
|
-
.slice(0, maxLength));
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Upstream instants (Trello card timestamps, effect-ledger times) are copied into the
|
|
147
|
-
* model-visible packet. Normalising them here means connector text can never ride
|
|
148
|
-
* along in a timestamp field and an unparseable value cannot fail packet validation
|
|
149
|
-
* after composition.
|
|
150
|
-
*/
|
|
151
|
-
function toIsoInstant(value) {
|
|
152
|
-
if (typeof value !== 'string' && typeof value !== 'number')
|
|
153
|
-
return null;
|
|
154
|
-
const ms = typeof value === 'number' ? value : Date.parse(value);
|
|
155
|
-
if (!Number.isFinite(ms))
|
|
156
|
-
return null;
|
|
157
|
-
return new Date(ms).toISOString();
|
|
158
|
-
}
|
|
159
|
-
function sanitizeWindow(window) {
|
|
160
|
-
return {
|
|
161
|
-
start: new Date(window.start).toISOString(),
|
|
162
|
-
end: new Date(window.end).toISOString(),
|
|
163
|
-
channelCount: window.channelCount,
|
|
164
|
-
messageCount: window.messageCount,
|
|
165
|
-
channels: window.channels
|
|
166
|
-
.map((channel) => ({
|
|
167
|
-
label: redactText(channel.label, 512),
|
|
168
|
-
count: channel.count,
|
|
169
|
-
excerpts: channel.excerpts.map((excerpt) => ({
|
|
170
|
-
authorLabel: redactText(excerpt.authorLabel, 160),
|
|
171
|
-
text: redactText(excerpt.text, 160),
|
|
172
|
-
observedAt: excerpt.observedAt === null ? null : new Date(excerpt.observedAt).toISOString(),
|
|
173
|
-
})),
|
|
174
|
-
}))
|
|
175
|
-
.sort((left, right) => right.count - left.count || left.label.localeCompare(right.label)),
|
|
176
|
-
triggerActivity: window.triggerActivity
|
|
177
|
-
.map((activity) => ({
|
|
178
|
-
kind: redactText(activity.kind, 160),
|
|
179
|
-
count: activity.count,
|
|
180
|
-
topics: activity.topics.map((topic) => redactText(topic, 256)).sort(),
|
|
181
|
-
}))
|
|
182
|
-
.sort((left, right) => right.count - left.count || left.kind.localeCompare(right.kind)),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function sourceLabel(sourceChannel, authorizedConnectors) {
|
|
186
|
-
if (!sourceChannel)
|
|
187
|
-
return null;
|
|
188
|
-
const separator = sourceChannel.indexOf(':');
|
|
189
|
-
if (separator <= 0)
|
|
190
|
-
return null;
|
|
191
|
-
const connector = sourceChannel.slice(0, separator);
|
|
192
|
-
return authorizedConnectors.includes(connector)
|
|
193
|
-
? (SOURCE_DISPLAY_LABELS[connector] ?? null)
|
|
194
|
-
: null;
|
|
195
|
-
}
|
|
196
|
-
function emptyCorrelation() {
|
|
197
|
-
return {
|
|
198
|
-
coverage: {
|
|
199
|
-
total: 0,
|
|
200
|
-
matched: 0,
|
|
201
|
-
unmatched: 0,
|
|
202
|
-
ambiguous: 0,
|
|
203
|
-
historical_only: 0,
|
|
204
|
-
not_applicable: 0,
|
|
205
|
-
},
|
|
206
|
-
rows: [],
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
function canonicalize(value) {
|
|
210
|
-
if (Array.isArray(value))
|
|
211
|
-
return value.map(canonicalize);
|
|
212
|
-
if (!isRecord(value))
|
|
213
|
-
return value;
|
|
214
|
-
return Object.fromEntries(Object.keys(value)
|
|
215
|
-
.sort()
|
|
216
|
-
.map((key) => [key, canonicalize(value[key])]));
|
|
217
|
-
}
|
|
218
|
-
function canonicalJson(packet) {
|
|
219
|
-
return JSON.stringify(canonicalize(packet));
|
|
220
|
-
}
|
|
221
|
-
function setPacketBytes(packet) {
|
|
222
|
-
let serialized = '';
|
|
223
|
-
let previous = -1;
|
|
224
|
-
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
225
|
-
serialized = canonicalJson(packet);
|
|
226
|
-
const bytes = Buffer.byteLength(serialized);
|
|
227
|
-
packet.packet.bytes = bytes;
|
|
228
|
-
if (bytes === previous)
|
|
229
|
-
return canonicalJson(packet);
|
|
230
|
-
previous = bytes;
|
|
231
|
-
}
|
|
232
|
-
throw new Error('Owner report context canonical byte count did not converge');
|
|
233
|
-
}
|
|
234
|
-
function markSourcePartial(packet, source, reason) {
|
|
235
|
-
const current = packet.sources[source];
|
|
236
|
-
packet.sources[source] = {
|
|
237
|
-
state: 'partial',
|
|
238
|
-
observedAt: current.observedAt,
|
|
239
|
-
reason,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function recomputeCorrelationCoverage(report) {
|
|
243
|
-
const coverage = {
|
|
244
|
-
total: report.rows.length,
|
|
245
|
-
matched: 0,
|
|
246
|
-
unmatched: 0,
|
|
247
|
-
ambiguous: 0,
|
|
248
|
-
historical_only: 0,
|
|
249
|
-
not_applicable: 0,
|
|
250
|
-
};
|
|
251
|
-
for (const row of report.rows)
|
|
252
|
-
coverage[row.outcome] += 1;
|
|
253
|
-
report.coverage = coverage;
|
|
254
|
-
}
|
|
255
|
-
function enforcePacketLimit(packet) {
|
|
256
|
-
let serialized = setPacketBytes(packet);
|
|
257
|
-
if (Buffer.byteLength(serialized) <= MAX_PACKET_BYTES)
|
|
258
|
-
return;
|
|
259
|
-
packet.packet.truncated = true;
|
|
260
|
-
if (!packet.caveats.includes('packet_size_truncated')) {
|
|
261
|
-
packet.caveats.push('packet_size_truncated');
|
|
262
|
-
}
|
|
263
|
-
const removers = [
|
|
264
|
-
() => {
|
|
265
|
-
for (let index = packet.trello.columns.length - 1; index >= 0; index -= 1) {
|
|
266
|
-
const column = packet.trello.columns[index];
|
|
267
|
-
if (column.cards.length > 0) {
|
|
268
|
-
column.cards.pop();
|
|
269
|
-
column.returned = column.cards.length;
|
|
270
|
-
packet.trello.truncated = true;
|
|
271
|
-
packet.trello.complete = false;
|
|
272
|
-
markSourcePartial(packet, 'trello', 'packet_size_limit_reached');
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return false;
|
|
277
|
-
},
|
|
278
|
-
() => {
|
|
279
|
-
if (packet.changes.rows.length === 0)
|
|
280
|
-
return false;
|
|
281
|
-
packet.changes.rows.pop();
|
|
282
|
-
packet.changes.returned = packet.changes.rows.length;
|
|
283
|
-
markSourcePartial(packet, 'changes', 'packet_size_limit_reached');
|
|
284
|
-
return true;
|
|
285
|
-
},
|
|
286
|
-
() => {
|
|
287
|
-
if (packet.currentClaims.length === 0)
|
|
288
|
-
return false;
|
|
289
|
-
packet.currentClaims.pop();
|
|
290
|
-
markSourcePartial(packet, 'claims', 'packet_size_limit_reached');
|
|
291
|
-
return true;
|
|
292
|
-
},
|
|
293
|
-
() => {
|
|
294
|
-
if (packet.tasks.length === 0)
|
|
295
|
-
return false;
|
|
296
|
-
packet.tasks.pop();
|
|
297
|
-
packet.taskCoverage.returned = packet.tasks.length;
|
|
298
|
-
packet.taskCoverage.truncated = packet.taskCoverage.total > packet.tasks.length;
|
|
299
|
-
markSourcePartial(packet, 'tasks', 'packet_size_limit_reached');
|
|
300
|
-
packet.correlations.rows = packet.correlations.rows.filter((row) => packet.tasks.some((task) => task.id === row.taskId));
|
|
301
|
-
recomputeCorrelationCoverage(packet.correlations);
|
|
302
|
-
return true;
|
|
303
|
-
},
|
|
304
|
-
() => {
|
|
305
|
-
if (packet.correlations.rows.length === 0)
|
|
306
|
-
return false;
|
|
307
|
-
packet.correlations.rows.pop();
|
|
308
|
-
recomputeCorrelationCoverage(packet.correlations);
|
|
309
|
-
return true;
|
|
310
|
-
},
|
|
311
|
-
() => {
|
|
312
|
-
for (let index = packet.windowEvidence.channels.length - 1; index >= 0; index -= 1) {
|
|
313
|
-
const channel = packet.windowEvidence.channels[index];
|
|
314
|
-
if (channel.excerpts.length > 0) {
|
|
315
|
-
channel.excerpts.pop();
|
|
316
|
-
return true;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return false;
|
|
320
|
-
},
|
|
321
|
-
() => {
|
|
322
|
-
for (let index = packet.windowEvidence.triggerActivity.length - 1; index >= 0; index -= 1) {
|
|
323
|
-
const activity = packet.windowEvidence.triggerActivity[index];
|
|
324
|
-
if (activity.topics.length > 0) {
|
|
325
|
-
activity.topics.pop();
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return false;
|
|
330
|
-
},
|
|
331
|
-
() => {
|
|
332
|
-
if (packet.windowEvidence.triggerActivity.length === 0)
|
|
333
|
-
return false;
|
|
334
|
-
packet.windowEvidence.triggerActivity.pop();
|
|
335
|
-
return true;
|
|
336
|
-
},
|
|
337
|
-
() => {
|
|
338
|
-
if (packet.trello.columns.length === 0)
|
|
339
|
-
return false;
|
|
340
|
-
packet.trello.columns.pop();
|
|
341
|
-
packet.trello.truncated = true;
|
|
342
|
-
packet.trello.complete = false;
|
|
343
|
-
markSourcePartial(packet, 'trello', 'packet_size_limit_reached');
|
|
344
|
-
return true;
|
|
345
|
-
},
|
|
346
|
-
() => {
|
|
347
|
-
if (packet.trello.boards.length === 0)
|
|
348
|
-
return false;
|
|
349
|
-
packet.trello.boards.pop();
|
|
350
|
-
packet.trello.truncated = true;
|
|
351
|
-
packet.trello.complete = false;
|
|
352
|
-
markSourcePartial(packet, 'trello', 'packet_size_limit_reached');
|
|
353
|
-
return true;
|
|
354
|
-
},
|
|
355
|
-
() => {
|
|
356
|
-
if (packet.windowEvidence.channels.length === 0)
|
|
357
|
-
return false;
|
|
358
|
-
packet.windowEvidence.channels.pop();
|
|
359
|
-
return true;
|
|
360
|
-
},
|
|
361
|
-
];
|
|
362
|
-
while (Buffer.byteLength(serialized) > MAX_PACKET_BYTES) {
|
|
363
|
-
let removed = false;
|
|
364
|
-
for (const remove of removers) {
|
|
365
|
-
if (remove()) {
|
|
366
|
-
removed = true;
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (!removed) {
|
|
371
|
-
throw new Error('Owner report context cannot fit the canonical packet byte limit');
|
|
372
|
-
}
|
|
373
|
-
serialized = setPacketBytes(packet);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
function hasExactKeys(value, keys) {
|
|
377
|
-
const actual = Object.keys(value);
|
|
378
|
-
return actual.length === keys.length && actual.every((key) => keys.includes(key));
|
|
379
|
-
}
|
|
380
|
-
function isBoundedString(value, maxLength) {
|
|
381
|
-
return typeof value === 'string' && value.length <= maxLength;
|
|
382
|
-
}
|
|
383
|
-
function isIsoString(value) {
|
|
384
|
-
return isBoundedString(value, 64) && isFiniteDate(value);
|
|
385
|
-
}
|
|
386
|
-
function isSourceState(value) {
|
|
387
|
-
if (!isRecord(value) ||
|
|
388
|
-
(value.state !== 'complete' && value.state !== 'partial' && value.state !== 'unavailable')) {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
if (value.state === 'complete') {
|
|
392
|
-
return hasExactKeys(value, ['state', 'observedAt']) && isIsoString(value.observedAt);
|
|
393
|
-
}
|
|
394
|
-
return (hasExactKeys(value, ['state', 'observedAt', 'reason']) &&
|
|
395
|
-
(value.state === 'partial'
|
|
396
|
-
? value.observedAt === null || isIsoString(value.observedAt)
|
|
397
|
-
: value.observedAt === null) &&
|
|
398
|
-
isBoundedString(value.reason, 160) &&
|
|
399
|
-
value.reason.length > 0);
|
|
400
|
-
}
|
|
401
|
-
function isReportWindowEvidence(value) {
|
|
402
|
-
if (!isRecord(value) ||
|
|
403
|
-
!hasExactKeys(value, [
|
|
404
|
-
'start',
|
|
405
|
-
'end',
|
|
406
|
-
'channelCount',
|
|
407
|
-
'messageCount',
|
|
408
|
-
'channels',
|
|
409
|
-
'triggerActivity',
|
|
410
|
-
]) ||
|
|
411
|
-
!isIsoString(value.start) ||
|
|
412
|
-
!isIsoString(value.end) ||
|
|
413
|
-
!isSafeCount(value.channelCount) ||
|
|
414
|
-
!isSafeCount(value.messageCount) ||
|
|
415
|
-
!Array.isArray(value.channels) ||
|
|
416
|
-
value.channels.length > 48 ||
|
|
417
|
-
!Array.isArray(value.triggerActivity) ||
|
|
418
|
-
value.triggerActivity.length > 100) {
|
|
419
|
-
return false;
|
|
420
|
-
}
|
|
421
|
-
const channelsValid = value.channels.every((channel) => {
|
|
422
|
-
if (!isRecord(channel) ||
|
|
423
|
-
!hasExactKeys(channel, ['label', 'count', 'excerpts']) ||
|
|
424
|
-
!isBoundedString(channel.label, 512) ||
|
|
425
|
-
!isSafeCount(channel.count) ||
|
|
426
|
-
!Array.isArray(channel.excerpts) ||
|
|
427
|
-
channel.excerpts.length > 5) {
|
|
428
|
-
return false;
|
|
429
|
-
}
|
|
430
|
-
return channel.excerpts.every((excerpt) => isRecord(excerpt) &&
|
|
431
|
-
hasExactKeys(excerpt, ['authorLabel', 'text', 'observedAt']) &&
|
|
432
|
-
isBoundedString(excerpt.authorLabel, 160) &&
|
|
433
|
-
isBoundedString(excerpt.text, 160) &&
|
|
434
|
-
(excerpt.observedAt === null || isIsoString(excerpt.observedAt)));
|
|
435
|
-
});
|
|
436
|
-
return (channelsValid &&
|
|
437
|
-
value.triggerActivity.every((activity) => isRecord(activity) &&
|
|
438
|
-
hasExactKeys(activity, ['kind', 'count', 'topics']) &&
|
|
439
|
-
isBoundedString(activity.kind, 160) &&
|
|
440
|
-
isSafeCount(activity.count) &&
|
|
441
|
-
Array.isArray(activity.topics) &&
|
|
442
|
-
activity.topics.every((topic) => isBoundedString(topic, 256))));
|
|
443
|
-
}
|
|
444
|
-
function isCurrentClaim(value) {
|
|
445
|
-
return (isRecord(value) &&
|
|
446
|
-
hasExactKeys(value, ['id', 'topic', 'summary', 'status', 'confidence']) &&
|
|
447
|
-
isBoundedString(value.id, 512) &&
|
|
448
|
-
isBoundedString(value.topic, 512) &&
|
|
449
|
-
isBoundedString(value.summary, 4_000) &&
|
|
450
|
-
isBoundedString(value.status, 64) &&
|
|
451
|
-
typeof value.confidence === 'number' &&
|
|
452
|
-
Number.isFinite(value.confidence));
|
|
453
|
-
}
|
|
454
|
-
function isOwnerTask(value) {
|
|
455
|
-
return (isRecord(value) &&
|
|
456
|
-
hasExactKeys(value, [
|
|
457
|
-
'id',
|
|
458
|
-
'revision',
|
|
459
|
-
'title',
|
|
460
|
-
'status',
|
|
461
|
-
'latestEvent',
|
|
462
|
-
'updatedAt',
|
|
463
|
-
'deadline',
|
|
464
|
-
'dueAt',
|
|
465
|
-
'sourceLabel',
|
|
466
|
-
]) &&
|
|
467
|
-
isSafeCount(value.id) &&
|
|
468
|
-
isSafeCount(value.revision) &&
|
|
469
|
-
isBoundedString(value.title, 1_000) &&
|
|
470
|
-
isBoundedString(value.status, 64) &&
|
|
471
|
-
(value.latestEvent === null || isBoundedString(value.latestEvent, 1_000)) &&
|
|
472
|
-
isIsoString(value.updatedAt) &&
|
|
473
|
-
(value.deadline === null ||
|
|
474
|
-
(typeof value.deadline === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value.deadline))) &&
|
|
475
|
-
(value.dueAt === null || isIsoString(value.dueAt)) &&
|
|
476
|
-
(value.sourceLabel === null ||
|
|
477
|
-
(isBoundedString(value.sourceLabel, 64) &&
|
|
478
|
-
SOURCE_DISPLAY_LABELS[value.sourceLabel] !== undefined)));
|
|
479
|
-
}
|
|
480
|
-
function isTrelloReport(value) {
|
|
481
|
-
if (!isRecord(value) ||
|
|
482
|
-
!hasExactKeys(value, ['observedAt', 'complete', 'truncated', 'boards', 'columns']) ||
|
|
483
|
-
!isIsoString(value.observedAt) ||
|
|
484
|
-
typeof value.complete !== 'boolean' ||
|
|
485
|
-
typeof value.truncated !== 'boolean' ||
|
|
486
|
-
!Array.isArray(value.boards) ||
|
|
487
|
-
!Array.isArray(value.columns)) {
|
|
488
|
-
return false;
|
|
489
|
-
}
|
|
490
|
-
const boardsValid = value.boards.every((board) => isRecord(board) &&
|
|
491
|
-
hasExactKeys(board, ['board', 'status', 'rosterDegraded']) &&
|
|
492
|
-
isBoundedString(board.board, 256) &&
|
|
493
|
-
(board.status === 'ok' || board.status === 'failed') &&
|
|
494
|
-
typeof board.rosterDegraded === 'boolean');
|
|
495
|
-
let cardTotal = 0;
|
|
496
|
-
const columnsValid = value.columns.every((column) => {
|
|
497
|
-
if (!isRecord(column) ||
|
|
498
|
-
!hasExactKeys(column, ['board', 'list', 'count', 'returned', 'cards']) ||
|
|
499
|
-
!isBoundedString(column.board, 256) ||
|
|
500
|
-
!isBoundedString(column.list, 256) ||
|
|
501
|
-
!isSafeCount(column.count) ||
|
|
502
|
-
!isSafeCount(column.returned) ||
|
|
503
|
-
!Array.isArray(column.cards) ||
|
|
504
|
-
column.cards.length > MAX_TRELLO_CARDS_PER_LIST ||
|
|
505
|
-
column.returned !== column.cards.length ||
|
|
506
|
-
column.count < column.returned) {
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
cardTotal += column.cards.length;
|
|
510
|
-
return column.cards.every((card) => isRecord(card) &&
|
|
511
|
-
hasExactKeys(card, ['name', 'labels', 'assignees', 'due', 'lastActivity']) &&
|
|
512
|
-
isBoundedString(card.name, 512) &&
|
|
513
|
-
Array.isArray(card.labels) &&
|
|
514
|
-
card.labels.length <= 20 &&
|
|
515
|
-
card.labels.every((label) => isBoundedString(label, 128)) &&
|
|
516
|
-
Array.isArray(card.assignees) &&
|
|
517
|
-
card.assignees.length <= 20 &&
|
|
518
|
-
card.assignees.every((assignee) => isBoundedString(assignee, 128)) &&
|
|
519
|
-
(card.due === null || isBoundedString(card.due, 64)) &&
|
|
520
|
-
isBoundedString(card.lastActivity, 64));
|
|
521
|
-
});
|
|
522
|
-
return boardsValid && columnsValid && cardTotal <= MAX_TRELLO_CARDS;
|
|
523
|
-
}
|
|
524
|
-
const CORRELATION_OUTCOMES = new Set([
|
|
525
|
-
'matched',
|
|
526
|
-
'unmatched',
|
|
527
|
-
'ambiguous',
|
|
528
|
-
'historical_only',
|
|
529
|
-
'not_applicable',
|
|
530
|
-
]);
|
|
531
|
-
const CORRELATION_REASONS = new Set([
|
|
532
|
-
'no_source',
|
|
533
|
-
'other_connector',
|
|
534
|
-
'no_provenance',
|
|
535
|
-
'provenance_not_indexed',
|
|
536
|
-
'provenance_connector_mismatch',
|
|
537
|
-
'external_ref_unresolvable',
|
|
538
|
-
'provenance_conflict',
|
|
539
|
-
'multiple_rows_one_item',
|
|
540
|
-
'absent_from_live_snapshot',
|
|
541
|
-
'live_snapshot_incomplete',
|
|
542
|
-
'live_item',
|
|
543
|
-
]);
|
|
544
|
-
function isCorrelationReport(value) {
|
|
545
|
-
if (!isRecord(value) ||
|
|
546
|
-
!hasExactKeys(value, ['coverage', 'rows']) ||
|
|
547
|
-
!isRecord(value.coverage) ||
|
|
548
|
-
!hasExactKeys(value.coverage, [
|
|
549
|
-
'total',
|
|
550
|
-
'matched',
|
|
551
|
-
'unmatched',
|
|
552
|
-
'ambiguous',
|
|
553
|
-
'historical_only',
|
|
554
|
-
'not_applicable',
|
|
555
|
-
]) ||
|
|
556
|
-
!Array.isArray(value.rows)) {
|
|
557
|
-
return false;
|
|
558
|
-
}
|
|
559
|
-
const counts = {
|
|
560
|
-
matched: 0,
|
|
561
|
-
unmatched: 0,
|
|
562
|
-
ambiguous: 0,
|
|
563
|
-
historical_only: 0,
|
|
564
|
-
not_applicable: 0,
|
|
565
|
-
};
|
|
566
|
-
const coverage = value.coverage;
|
|
567
|
-
const rowsValid = value.rows.every((row) => {
|
|
568
|
-
if (!isRecord(row) ||
|
|
569
|
-
!hasExactKeys(row, ['taskId', 'outcome', 'reason', 'live']) ||
|
|
570
|
-
!isSafeCount(row.taskId) ||
|
|
571
|
-
typeof row.outcome !== 'string' ||
|
|
572
|
-
!CORRELATION_OUTCOMES.has(row.outcome) ||
|
|
573
|
-
typeof row.reason !== 'string' ||
|
|
574
|
-
!CORRELATION_REASONS.has(row.reason) ||
|
|
575
|
-
!(row.live === null ||
|
|
576
|
-
(isRecord(row.live) &&
|
|
577
|
-
hasExactKeys(row.live, ['board', 'list']) &&
|
|
578
|
-
isBoundedString(row.live.board, 256) &&
|
|
579
|
-
isBoundedString(row.live.list, 256)))) {
|
|
580
|
-
return false;
|
|
581
|
-
}
|
|
582
|
-
counts[row.outcome] += 1;
|
|
583
|
-
return true;
|
|
584
|
-
});
|
|
585
|
-
return (rowsValid &&
|
|
586
|
-
isSafeCount(coverage.total) &&
|
|
587
|
-
coverage.total === value.rows.length &&
|
|
588
|
-
[...CORRELATION_OUTCOMES].every((outcome) => isSafeCount(coverage[outcome]) && coverage[outcome] === counts[outcome]));
|
|
589
|
-
}
|
|
590
|
-
function isChangesReport(value) {
|
|
591
|
-
return (isRecord(value) &&
|
|
592
|
-
hasExactKeys(value, ['since', 'total', 'returned', 'coverage', 'rows']) &&
|
|
593
|
-
isIsoString(value.since) &&
|
|
594
|
-
isSafeCount(value.total) &&
|
|
595
|
-
isSafeCount(value.returned) &&
|
|
596
|
-
isRecord(value.coverage) &&
|
|
597
|
-
hasExactKeys(value.coverage, ['attributed', 'unattributed']) &&
|
|
598
|
-
isSafeCount(value.coverage.attributed) &&
|
|
599
|
-
isSafeCount(value.coverage.unattributed) &&
|
|
600
|
-
Array.isArray(value.rows) &&
|
|
601
|
-
value.rows.length <= MAX_CHANGES &&
|
|
602
|
-
value.returned === value.rows.length &&
|
|
603
|
-
value.total >= value.returned &&
|
|
604
|
-
value.rows.every((row) => isRecord(row) &&
|
|
605
|
-
hasExactKeys(row, ['kind', 'targetType', 'causeState', 'causeKind', 'at']) &&
|
|
606
|
-
isBoundedString(row.kind, 160) &&
|
|
607
|
-
isBoundedString(row.targetType, 160) &&
|
|
608
|
-
(row.causeState === 'attributed' || row.causeState === 'unattributed') &&
|
|
609
|
-
isBoundedString(row.causeKind, 160) &&
|
|
610
|
-
isIsoString(row.at)));
|
|
611
|
-
}
|
|
612
|
-
function isOwnerReportContext(value) {
|
|
613
|
-
if (!isRecord(value) ||
|
|
614
|
-
!hasExactKeys(value, [
|
|
615
|
-
'schemaVersion',
|
|
616
|
-
'observedAt',
|
|
617
|
-
'windowEvidence',
|
|
618
|
-
'sources',
|
|
619
|
-
'packet',
|
|
620
|
-
'taskCoverage',
|
|
621
|
-
'currentClaims',
|
|
622
|
-
'tasks',
|
|
623
|
-
'trello',
|
|
624
|
-
'correlations',
|
|
625
|
-
'changes',
|
|
626
|
-
'caveats',
|
|
627
|
-
'operationalIssues',
|
|
628
|
-
]) ||
|
|
629
|
-
value.schemaVersion !== 'mama.owner-report-context/v1' ||
|
|
630
|
-
!isIsoString(value.observedAt) ||
|
|
631
|
-
!isReportWindowEvidence(value.windowEvidence) ||
|
|
632
|
-
!isRecord(value.sources) ||
|
|
633
|
-
!hasExactKeys(value.sources, ['claims', 'tasks', 'trello', 'changes']) ||
|
|
634
|
-
!Object.values(value.sources).every(isSourceState) ||
|
|
635
|
-
!isRecord(value.packet) ||
|
|
636
|
-
!hasExactKeys(value.packet, ['bytes', 'truncated']) ||
|
|
637
|
-
!isSafeCount(value.packet.bytes) ||
|
|
638
|
-
typeof value.packet.truncated !== 'boolean' ||
|
|
639
|
-
!isRecord(value.taskCoverage) ||
|
|
640
|
-
!hasExactKeys(value.taskCoverage, ['total', 'returned', 'truncated']) ||
|
|
641
|
-
!isSafeCount(value.taskCoverage.total) ||
|
|
642
|
-
!isSafeCount(value.taskCoverage.returned) ||
|
|
643
|
-
value.taskCoverage.total < value.taskCoverage.returned ||
|
|
644
|
-
typeof value.taskCoverage.truncated !== 'boolean' ||
|
|
645
|
-
!Array.isArray(value.currentClaims) ||
|
|
646
|
-
value.currentClaims.length > MAX_CLAIMS ||
|
|
647
|
-
!value.currentClaims.every(isCurrentClaim) ||
|
|
648
|
-
!Array.isArray(value.tasks) ||
|
|
649
|
-
!value.tasks.every(isOwnerTask) ||
|
|
650
|
-
!isTrelloReport(value.trello) ||
|
|
651
|
-
!isCorrelationReport(value.correlations) ||
|
|
652
|
-
!isChangesReport(value.changes) ||
|
|
653
|
-
!Array.isArray(value.caveats) ||
|
|
654
|
-
!value.caveats.every((caveat) => isBoundedString(caveat, 160)) ||
|
|
655
|
-
!Array.isArray(value.operationalIssues) ||
|
|
656
|
-
value.operationalIssues.length > exports.MAX_OPERATIONAL_ISSUES ||
|
|
657
|
-
!value.operationalIssues.every(isOperationalIssueSummary)) {
|
|
658
|
-
return false;
|
|
659
|
-
}
|
|
660
|
-
return true;
|
|
661
|
-
}
|
|
662
|
-
function isOperationalIssueSummary(value) {
|
|
663
|
-
if (typeof value !== 'object' || value === null) {
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
const issue = value;
|
|
667
|
-
return (isBoundedString(issue.issueId, 64) &&
|
|
668
|
-
isBoundedString(issue.surface, 32) &&
|
|
669
|
-
(issue.severity === 'info' || issue.severity === 'warn' || issue.severity === 'error') &&
|
|
670
|
-
isBoundedString(issue.summary, 300) &&
|
|
671
|
-
Number.isSafeInteger(issue.occurrences) &&
|
|
672
|
-
issue.occurrences >= 1 &&
|
|
673
|
-
isBoundedString(issue.firstSeenAt, 40));
|
|
674
|
-
}
|
|
675
|
-
function serializeOwnerReportContext(packet) {
|
|
676
|
-
if (!isOwnerReportContext(packet)) {
|
|
677
|
-
throw new Error('Invalid owner report context packet');
|
|
678
|
-
}
|
|
679
|
-
const detached = JSON.parse(JSON.stringify(packet));
|
|
680
|
-
const serialized = canonicalJson(detached);
|
|
681
|
-
const bytes = Buffer.byteLength(serialized);
|
|
682
|
-
if (bytes > MAX_PACKET_BYTES || detached.packet.bytes !== bytes) {
|
|
683
|
-
throw new Error('Invalid owner report context packet');
|
|
684
|
-
}
|
|
685
|
-
return serialized;
|
|
686
|
-
}
|
|
687
|
-
exports.MAX_OPERATIONAL_ISSUES = 20;
|
|
688
|
-
const SEVERITY_ORDER = { error: 0, warn: 1, info: 2 };
|
|
689
|
-
/** The system's open failures, redacted upstream, highest severity first, bounded. */
|
|
690
|
-
function readOperationalIssuesBounded(deps) {
|
|
691
|
-
if (!deps.readOperationalIssues) {
|
|
692
|
-
return [];
|
|
693
|
-
}
|
|
694
|
-
return [...deps.readOperationalIssues()]
|
|
695
|
-
.sort((a, b) => (SEVERITY_ORDER[a.severity] ?? 3) - (SEVERITY_ORDER[b.severity] ?? 3))
|
|
696
|
-
.slice(0, exports.MAX_OPERATIONAL_ISSUES)
|
|
697
|
-
.map((issue) => ({
|
|
698
|
-
issueId: redactText(issue.issueId, 64),
|
|
699
|
-
surface: redactText(issue.surface, 32),
|
|
700
|
-
severity: issue.severity,
|
|
701
|
-
summary: redactText(issue.summary, 300),
|
|
702
|
-
occurrences: issue.occurrences,
|
|
703
|
-
firstSeenAt: issue.firstSeenAt,
|
|
704
|
-
}));
|
|
705
|
-
}
|
|
706
|
-
async function compileOwnerReportContext(input, deps) {
|
|
707
|
-
assertInput(input);
|
|
708
|
-
const nowMs = deps.now();
|
|
709
|
-
if (!Number.isSafeInteger(nowMs) || nowMs < 0) {
|
|
710
|
-
throw new Error('Invalid owner report context clock');
|
|
711
|
-
}
|
|
712
|
-
const observedAt = new Date(nowMs).toISOString();
|
|
713
|
-
const readScope = detachScope(input.readScope);
|
|
714
|
-
const caveats = [];
|
|
715
|
-
const taskRows = [];
|
|
716
|
-
let taskTotal = 0;
|
|
717
|
-
let taskState = { state: 'complete', observedAt };
|
|
718
|
-
try {
|
|
719
|
-
let cursor;
|
|
720
|
-
const seenCursors = new Set();
|
|
721
|
-
do {
|
|
722
|
-
// Real-data verification (2026-09-02): 264 open owner tasks against a 50-row
|
|
723
|
-
// bound. Deadline-first ranking filled the packet with month-old deadline
|
|
724
|
-
// rows and displaced every task updated in the last week, including all
|
|
725
|
-
// in_progress work - the owner report answers "what is open NOW", so recency
|
|
726
|
-
// ranks the bounded set. Stale deadline work stays the Board lane's job.
|
|
727
|
-
const page = deps.listTaskPage({
|
|
728
|
-
includeTerminal: false,
|
|
729
|
-
order: 'updated',
|
|
730
|
-
limit: TASK_PAGE_SIZE,
|
|
731
|
-
...(cursor ? { cursor } : {}),
|
|
732
|
-
});
|
|
733
|
-
if (!isSafeCount(page.total) || !Array.isArray(page.tasks)) {
|
|
734
|
-
throw new Error('invalid task page');
|
|
735
|
-
}
|
|
736
|
-
if (taskRows.length === 0)
|
|
737
|
-
taskTotal = page.total;
|
|
738
|
-
taskRows.push(...page.tasks);
|
|
739
|
-
const next = page.nextCursor ?? undefined;
|
|
740
|
-
if (next && seenCursors.has(next))
|
|
741
|
-
throw new Error('task cursor repeated');
|
|
742
|
-
if (next)
|
|
743
|
-
seenCursors.add(next);
|
|
744
|
-
cursor = next;
|
|
745
|
-
} while (cursor);
|
|
746
|
-
if (taskTotal > taskRows.length) {
|
|
747
|
-
taskState = { state: 'partial', observedAt, reason: 'task_limit_reached' };
|
|
748
|
-
caveats.push('task_set_truncated');
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
catch {
|
|
752
|
-
taskState =
|
|
753
|
-
taskRows.length > 0
|
|
754
|
-
? { state: 'partial', observedAt, reason: 'tasks_read_failed' }
|
|
755
|
-
: { state: 'unavailable', observedAt: null, reason: 'tasks_read_failed' };
|
|
756
|
-
caveats.push('tasks_unavailable');
|
|
757
|
-
}
|
|
758
|
-
let claims = [];
|
|
759
|
-
let claimsState = { state: 'complete', observedAt };
|
|
760
|
-
try {
|
|
761
|
-
claims = await deps.readClaims(readScope);
|
|
762
|
-
}
|
|
763
|
-
catch {
|
|
764
|
-
claimsState = { state: 'unavailable', observedAt: null, reason: 'claims_read_failed' };
|
|
765
|
-
caveats.push('claims_unavailable');
|
|
766
|
-
}
|
|
767
|
-
claims.sort((left, right) => (right.updated_at ?? right.created_at ?? 0) - (left.updated_at ?? left.created_at ?? 0) ||
|
|
768
|
-
left.memory_id.localeCompare(right.memory_id));
|
|
769
|
-
if (claims.length > MAX_CLAIMS) {
|
|
770
|
-
claims = claims.slice(0, MAX_CLAIMS);
|
|
771
|
-
claimsState = { state: 'partial', observedAt, reason: 'claims_limit_reached' };
|
|
772
|
-
caveats.push('claims_truncated');
|
|
773
|
-
}
|
|
774
|
-
let rawTrello = null;
|
|
775
|
-
let trelloState;
|
|
776
|
-
if (!readScope.rawConnectors.includes('trello')) {
|
|
777
|
-
trelloState = { state: 'unavailable', observedAt: null, reason: 'trello_not_authorized' };
|
|
778
|
-
caveats.push('trello_unavailable');
|
|
779
|
-
}
|
|
780
|
-
else {
|
|
781
|
-
try {
|
|
782
|
-
rawTrello = await deps.readTrello(readScope);
|
|
783
|
-
if (rawTrello.boards.length === 0) {
|
|
784
|
-
trelloState = {
|
|
785
|
-
state: 'unavailable',
|
|
786
|
-
observedAt: null,
|
|
787
|
-
reason: 'trello_no_authorized_boards',
|
|
788
|
-
};
|
|
789
|
-
caveats.push('trello_unavailable');
|
|
790
|
-
}
|
|
791
|
-
else if (!rawTrello.complete || rawTrello.truncated) {
|
|
792
|
-
trelloState = {
|
|
793
|
-
state: 'partial',
|
|
794
|
-
observedAt: rawTrello.observedAt,
|
|
795
|
-
reason: 'trello_snapshot_incomplete',
|
|
796
|
-
};
|
|
797
|
-
caveats.push('trello_snapshot_incomplete');
|
|
798
|
-
}
|
|
799
|
-
else {
|
|
800
|
-
trelloState = { state: 'complete', observedAt: rawTrello.observedAt };
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
catch {
|
|
804
|
-
trelloState = { state: 'unavailable', observedAt: null, reason: 'trello_read_failed' };
|
|
805
|
-
caveats.push('trello_unavailable');
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
const trello = {
|
|
809
|
-
observedAt: rawTrello?.observedAt ?? observedAt,
|
|
810
|
-
complete: rawTrello !== null && rawTrello.boards.length > 0 && rawTrello.complete,
|
|
811
|
-
truncated: rawTrello?.truncated ?? false,
|
|
812
|
-
boards: (rawTrello?.boards ?? [])
|
|
813
|
-
.map((board) => ({
|
|
814
|
-
board: redactText(board.board, 256),
|
|
815
|
-
status: board.status,
|
|
816
|
-
rosterDegraded: board.rosterDegraded,
|
|
817
|
-
}))
|
|
818
|
-
.sort((left, right) => left.board.localeCompare(right.board)),
|
|
819
|
-
columns: [],
|
|
820
|
-
};
|
|
821
|
-
let remainingCards = MAX_TRELLO_CARDS;
|
|
822
|
-
for (const column of [...(rawTrello?.columns ?? [])].sort((left, right) => left.board.localeCompare(right.board) || left.list.localeCompare(right.list))) {
|
|
823
|
-
const sortedCards = [...column.cards].sort((left, right) => left.name.localeCompare(right.name) || left.lastActivity.localeCompare(right.lastActivity));
|
|
824
|
-
const allowed = Math.max(0, Math.min(MAX_TRELLO_CARDS_PER_LIST, remainingCards));
|
|
825
|
-
const cards = sortedCards.slice(0, allowed).map((card) => ({
|
|
826
|
-
name: redactText(card.name, 512),
|
|
827
|
-
labels: card.labels
|
|
828
|
-
.slice(0, 20)
|
|
829
|
-
.map((label) => redactText(label, 128))
|
|
830
|
-
.sort(),
|
|
831
|
-
assignees: card.assignees
|
|
832
|
-
.slice(0, 20)
|
|
833
|
-
.map((assignee) => redactText(assignee, 128))
|
|
834
|
-
.sort(),
|
|
835
|
-
due: toIsoInstant(card.due),
|
|
836
|
-
lastActivity: toIsoInstant(card.lastActivity) ?? observedAt,
|
|
837
|
-
}));
|
|
838
|
-
remainingCards -= cards.length;
|
|
839
|
-
if (cards.length < column.cards.length || cards.length < column.returned) {
|
|
840
|
-
trello.truncated = true;
|
|
841
|
-
trello.complete = false;
|
|
842
|
-
}
|
|
843
|
-
trello.columns.push({
|
|
844
|
-
board: redactText(column.board, 256),
|
|
845
|
-
list: redactText(column.list, 256),
|
|
846
|
-
count: column.count,
|
|
847
|
-
returned: cards.length,
|
|
848
|
-
cards,
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
if (trello.truncated && trelloState.state !== 'unavailable') {
|
|
852
|
-
trelloState = {
|
|
853
|
-
state: 'partial',
|
|
854
|
-
observedAt: rawTrello?.observedAt ?? null,
|
|
855
|
-
reason: 'trello_card_limit_reached',
|
|
856
|
-
};
|
|
857
|
-
if (!caveats.includes('trello_snapshot_incomplete'))
|
|
858
|
-
caveats.push('trello_snapshot_incomplete');
|
|
859
|
-
}
|
|
860
|
-
let correlation = emptyCorrelation();
|
|
861
|
-
try {
|
|
862
|
-
const lookupProvenance = rawTrello === null ? () => null : await deps.buildProvenanceLookup();
|
|
863
|
-
const rawResult = deps.correlate({
|
|
864
|
-
connector: 'trello',
|
|
865
|
-
rows: taskRows.map((row) => ({
|
|
866
|
-
id: row.id,
|
|
867
|
-
sourceChannel: row.sourceChannel,
|
|
868
|
-
sourceEventId: row.sourceEventId,
|
|
869
|
-
})),
|
|
870
|
-
lookupProvenance,
|
|
871
|
-
liveItems: (rawTrello?.columns ?? []).flatMap((column) => column.cards.map((card) => ({
|
|
872
|
-
itemId: card.cardId,
|
|
873
|
-
board: column.board,
|
|
874
|
-
list: column.list,
|
|
875
|
-
}))),
|
|
876
|
-
liveSnapshotComplete: trelloState.state === 'complete',
|
|
877
|
-
});
|
|
878
|
-
correlation = {
|
|
879
|
-
coverage: { ...rawResult.coverage },
|
|
880
|
-
rows: rawResult.correlations
|
|
881
|
-
.map((row) => ({
|
|
882
|
-
taskId: row.taskId,
|
|
883
|
-
outcome: row.outcome,
|
|
884
|
-
reason: row.reason,
|
|
885
|
-
live: row.live
|
|
886
|
-
? { board: redactText(row.live.board, 256), list: redactText(row.live.list, 256) }
|
|
887
|
-
: null,
|
|
888
|
-
}))
|
|
889
|
-
.sort((left, right) => left.taskId - right.taskId),
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
catch {
|
|
893
|
-
caveats.push('correlation_unavailable');
|
|
894
|
-
}
|
|
895
|
-
let changes = {
|
|
896
|
-
since: new Date(input.since).toISOString(),
|
|
897
|
-
total: 0,
|
|
898
|
-
returned: 0,
|
|
899
|
-
coverage: { attributed: 0, unattributed: 0 },
|
|
900
|
-
rows: [],
|
|
901
|
-
};
|
|
902
|
-
let changesState = { state: 'complete', observedAt };
|
|
903
|
-
try {
|
|
904
|
-
const rawChanges = deps.readChanges(readScope, { since: input.since, limit: MAX_CHANGES }, nowMs);
|
|
905
|
-
if (!rawChanges.success) {
|
|
906
|
-
changesState = { state: 'unavailable', observedAt: null, reason: 'changes_read_failed' };
|
|
907
|
-
caveats.push('changes_unavailable');
|
|
908
|
-
}
|
|
909
|
-
else {
|
|
910
|
-
const normalizedRows = rawChanges.changes.slice(0, MAX_CHANGES).map((change) => ({
|
|
911
|
-
kind: redactText(change.kind, 160),
|
|
912
|
-
targetType: redactText(change.target_type, 160),
|
|
913
|
-
causeState: change.cause_state,
|
|
914
|
-
causeKind: redactText(change.cause_kind, 160),
|
|
915
|
-
at: toIsoInstant(change.at),
|
|
916
|
-
}));
|
|
917
|
-
const rows = normalizedRows.flatMap((row) => row.at === null ? [] : [{ ...row, at: row.at }]);
|
|
918
|
-
changes = {
|
|
919
|
-
since: rawChanges.since,
|
|
920
|
-
total: rawChanges.total,
|
|
921
|
-
returned: rows.length,
|
|
922
|
-
coverage: { ...rawChanges.coverage },
|
|
923
|
-
rows,
|
|
924
|
-
};
|
|
925
|
-
if (rows.length < normalizedRows.length) {
|
|
926
|
-
// A change whose time cannot be read is not silently redated; the source is
|
|
927
|
-
// marked partial so the report says the ledger was not fully readable.
|
|
928
|
-
changesState = { state: 'partial', observedAt, reason: 'changes_row_time_invalid' };
|
|
929
|
-
caveats.push('changes_rows_dropped');
|
|
930
|
-
}
|
|
931
|
-
else if (rawChanges.total > rows.length || rawChanges.returned > rows.length) {
|
|
932
|
-
changesState = { state: 'partial', observedAt, reason: 'changes_limit_reached' };
|
|
933
|
-
caveats.push('changes_truncated');
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
catch {
|
|
938
|
-
changesState = { state: 'unavailable', observedAt: null, reason: 'changes_read_failed' };
|
|
939
|
-
caveats.push('changes_unavailable');
|
|
940
|
-
}
|
|
941
|
-
const windowEvidence = sanitizeWindow(input.windowEvidence);
|
|
942
|
-
if (windowEvidence.channels.some((channel) => channel.excerpts.some((item) => item.observedAt === null))) {
|
|
943
|
-
caveats.push('window_evidence_timestamp_unavailable');
|
|
944
|
-
}
|
|
945
|
-
const packet = {
|
|
946
|
-
schemaVersion: 'mama.owner-report-context/v1',
|
|
947
|
-
observedAt,
|
|
948
|
-
windowEvidence,
|
|
949
|
-
sources: {
|
|
950
|
-
claims: claimsState,
|
|
951
|
-
tasks: taskState,
|
|
952
|
-
trello: trelloState,
|
|
953
|
-
changes: changesState,
|
|
954
|
-
},
|
|
955
|
-
packet: { bytes: 0, truncated: false },
|
|
956
|
-
taskCoverage: {
|
|
957
|
-
total: taskTotal,
|
|
958
|
-
returned: taskRows.length,
|
|
959
|
-
truncated: taskTotal > taskRows.length,
|
|
960
|
-
},
|
|
961
|
-
operationalIssues: readOperationalIssuesBounded(deps),
|
|
962
|
-
currentClaims: claims.map((row) => ({
|
|
963
|
-
id: redactText(row.memory_id, 512),
|
|
964
|
-
topic: redactText(row.topic, 512),
|
|
965
|
-
summary: redactText(row.effective_summary, 4_000),
|
|
966
|
-
status: redactText(row.truth_status, 64),
|
|
967
|
-
confidence: row.trust_score,
|
|
968
|
-
})),
|
|
969
|
-
tasks: taskRows.map((row) => ({
|
|
970
|
-
id: row.id,
|
|
971
|
-
revision: row.revision,
|
|
972
|
-
title: redactText(row.title, 1_000),
|
|
973
|
-
status: redactText(row.status, 64),
|
|
974
|
-
latestEvent: row.latestEvent === null ? null : redactText(row.latestEvent, 1_000),
|
|
975
|
-
updatedAt: new Date(row.updatedAt).toISOString(),
|
|
976
|
-
deadline: row.deadlineIso,
|
|
977
|
-
dueAt: row.dueAt === null ? null : new Date(row.dueAt).toISOString(),
|
|
978
|
-
sourceLabel: sourceLabel(row.sourceChannel, readScope.rawConnectors),
|
|
979
|
-
})),
|
|
980
|
-
trello,
|
|
981
|
-
correlations: correlation,
|
|
982
|
-
changes,
|
|
983
|
-
caveats: [...new Set(caveats)].sort(),
|
|
984
|
-
};
|
|
985
|
-
const detached = JSON.parse(JSON.stringify(packet));
|
|
986
|
-
enforcePacketLimit(detached);
|
|
987
|
-
setPacketBytes(detached);
|
|
988
|
-
if (!isOwnerReportContext(detached)) {
|
|
989
|
-
throw new Error('Owner report context failed canonical schema validation');
|
|
990
|
-
}
|
|
991
|
-
return detached;
|
|
992
|
-
}
|
|
993
|
-
/**
|
|
994
|
-
* One MAMA (Phase 1 Task 3): the event turn starts from the same host-compiled
|
|
995
|
-
* packet as the report, narrowed to its channel. The read scope MUST be the
|
|
996
|
-
* owner-event scope the run's envelope holds - reading under another principal
|
|
997
|
-
* is the mismatch that fails the envelope scope audit.
|
|
998
|
-
*/
|
|
999
|
-
async function compileChannelPacket(input, deps) {
|
|
1000
|
-
const separator = input.channelKey.indexOf(':');
|
|
1001
|
-
const connector = separator > 0 ? input.channelKey.slice(0, separator) : input.channelKey;
|
|
1002
|
-
// The packet validator only accepts sourceLabels from SOURCE_DISPLAY_LABELS, so an
|
|
1003
|
-
// unknown connector fails OPEN: keep every row rather than filter on a label no
|
|
1004
|
-
// row can carry.
|
|
1005
|
-
const label = SOURCE_DISPLAY_LABELS[connector] ?? null;
|
|
1006
|
-
const end = new Date(deps.now()).toISOString();
|
|
1007
|
-
const full = await compileOwnerReportContext({
|
|
1008
|
-
readScope: input.readScope,
|
|
1009
|
-
since: input.since,
|
|
1010
|
-
windowEvidence: {
|
|
1011
|
-
start: input.since,
|
|
1012
|
-
end,
|
|
1013
|
-
channelCount: 1,
|
|
1014
|
-
messageCount: input.eventIds.length,
|
|
1015
|
-
channels: [{ label: label ?? connector, count: input.eventIds.length, excerpts: [] }],
|
|
1016
|
-
triggerActivity: [],
|
|
1017
|
-
},
|
|
1018
|
-
}, deps);
|
|
1019
|
-
const keep = new Set(full.tasks
|
|
1020
|
-
.filter((row) => label === null ||
|
|
1021
|
-
row.sourceLabel === label ||
|
|
1022
|
-
row.status === 'in_progress' ||
|
|
1023
|
-
row.status === 'review')
|
|
1024
|
-
.map((row) => row.id));
|
|
1025
|
-
const tasks = full.tasks.filter((row) => keep.has(row.id));
|
|
1026
|
-
const rows = full.correlations.rows.filter((row) => keep.has(row.taskId));
|
|
1027
|
-
// The recency bound is applied to the whole ledger before this channel filter, so a
|
|
1028
|
-
// channel whose rows were not updated recently can come out empty while its tasks
|
|
1029
|
-
// exist. Say so, or the turn will create the task a second time.
|
|
1030
|
-
const caveats = tasks.length === 0 && full.taskCoverage.total > 0
|
|
1031
|
-
? [
|
|
1032
|
-
...full.caveats,
|
|
1033
|
-
// Bounded to the caveat cap (160 chars) even for the longest label.
|
|
1034
|
-
`channel_has_no_matching_task: ${(label ?? connector).slice(0, 24)} has no task among the ${full.tasks.length} board rows; task_list allowed`,
|
|
1035
|
-
]
|
|
1036
|
-
: full.caveats;
|
|
1037
|
-
const packet = {
|
|
1038
|
-
...full,
|
|
1039
|
-
caveats,
|
|
1040
|
-
tasks,
|
|
1041
|
-
taskCoverage: {
|
|
1042
|
-
total: full.taskCoverage.total,
|
|
1043
|
-
returned: tasks.length,
|
|
1044
|
-
truncated: full.taskCoverage.truncated,
|
|
1045
|
-
},
|
|
1046
|
-
correlations: { coverage: { ...full.correlations.coverage }, rows },
|
|
1047
|
-
};
|
|
1048
|
-
recomputeCorrelationCoverage(packet.correlations);
|
|
1049
|
-
setPacketBytes(packet);
|
|
1050
|
-
return packet;
|
|
1051
|
-
}
|
|
1052
|
-
//# sourceMappingURL=report-context.js.map
|