@the-open-engine/zeroshot 6.39.2 → 6.40.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/README.md +12 -0
- package/cli/agent-provider-boundary.js +19 -0
- package/cli/agent-provider-boundary.ts +79 -0
- package/cli/export-stream.js +94 -0
- package/cli/export-stream.ts +118 -0
- package/cli/index.js +102 -54
- package/cli/json-export.js +2 -38
- package/cli/json-export.ts +2 -56
- package/cli/semantic-canonical.js +60 -0
- package/cli/semantic-canonical.ts +59 -0
- package/cli/semantic-contract.js +97 -0
- package/cli/semantic-contract.ts +153 -0
- package/cli/semantic-events.js +53 -0
- package/cli/semantic-events.ts +65 -0
- package/cli/semantic-evidence.js +63 -0
- package/cli/semantic-evidence.ts +65 -0
- package/cli/semantic-export.js +186 -0
- package/cli/semantic-export.ts +245 -0
- package/cli/semantic-json.js +69 -0
- package/cli/semantic-json.ts +69 -0
- package/cli/semantic-line-scanner.js +56 -0
- package/cli/semantic-line-scanner.ts +54 -0
- package/cli/semantic-parser.js +78 -0
- package/cli/semantic-parser.ts +95 -0
- package/cli/semantic-provider-line.js +91 -0
- package/cli/semantic-provider-line.ts +106 -0
- package/cli/trace-evidence.js +86 -0
- package/cli/trace-evidence.ts +115 -0
- package/cli/trace-export.js +120 -0
- package/cli/trace-export.ts +164 -0
- package/cli/trace-output-record.js +15 -0
- package/cli/trace-output-record.ts +18 -0
- package/cli/trace-output.js +98 -0
- package/cli/trace-output.ts +119 -0
- package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -1
- package/lib/agent-cli-provider/log-prefix.js +8 -6
- package/lib/agent-cli-provider/log-prefix.js.map +1 -1
- package/lib/stream-json-parser.js +6 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +6 -4
- package/src/agent/agent-task-executor.js +36 -40
- package/src/agent/output-extraction-json.js +5 -6
- package/src/agent/output-extraction-json.ts +4 -7
- package/src/agent-cli-provider/log-prefix.ts +12 -7
- package/src/claude-task-runner.js +9 -9
- package/src/legacy-lib/stream-json-parser.ts +10 -5
- package/src/providers/index.js +1 -19
- package/src/task-log-line.d.ts +20 -0
- package/src/task-log-line.js +80 -0
- package/task-lib/commands/logs.js +5 -8
- package/task-lib/sdk-watcher-output.js +31 -0
- package/task-lib/sdk-watcher.js +4 -11
- package/task-lib/watcher-output-runtime.js +30 -16
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const agent_provider_boundary_1 = require("./agent-provider-boundary");
|
|
5
|
+
const export_stream_1 = require("./export-stream");
|
|
6
|
+
const semantic_contract_1 = require("./semantic-contract");
|
|
7
|
+
const semantic_evidence_1 = require("./semantic-evidence");
|
|
8
|
+
const semantic_parser_1 = require("./semantic-parser");
|
|
9
|
+
const trace_evidence_1 = require("./trace-evidence");
|
|
10
|
+
const trace_output_1 = require("./trace-output");
|
|
11
|
+
function assertOutputIsNotTaskLog(outputPath, allowedLogRoot, taskIds) {
|
|
12
|
+
if (!outputPath)
|
|
13
|
+
return;
|
|
14
|
+
const resolvedOutput = path.resolve(outputPath);
|
|
15
|
+
for (const taskId of taskIds) {
|
|
16
|
+
if ((0, trace_evidence_1.expectedLogPath)(allowedLogRoot, taskId) === resolvedOutput) {
|
|
17
|
+
throw new Error('Semantic export output cannot replace a source task log');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function resolveAdapter(provider) {
|
|
22
|
+
if (!provider)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return (0, agent_provider_boundary_1.getProviderAdapter)(provider);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function taskPrompt(task) {
|
|
32
|
+
if (!task)
|
|
33
|
+
return null;
|
|
34
|
+
return (0, export_stream_1.nullableString)(task.fullPrompt) ?? (0, export_stream_1.nullableString)(task.prompt);
|
|
35
|
+
}
|
|
36
|
+
function writeTaskRecord(options) {
|
|
37
|
+
const { writer, taskId, agentId, task, adapter, evidence } = options;
|
|
38
|
+
writer.write({
|
|
39
|
+
record_type: 'task',
|
|
40
|
+
task_id: taskId,
|
|
41
|
+
agent_id: agentId,
|
|
42
|
+
provider: (0, export_stream_1.nullableString)(task?.provider),
|
|
43
|
+
adapter_id: adapter?.id ?? null,
|
|
44
|
+
adapter_version: adapter?.adapterVersion ?? null,
|
|
45
|
+
model: (0, export_stream_1.nullableString)(task?.model),
|
|
46
|
+
prompt_ref: (0, trace_evidence_1.logicalTaskRef)(taskId, 'prompt'),
|
|
47
|
+
prompt: taskPrompt(task),
|
|
48
|
+
raw_output_ref: (0, trace_evidence_1.logicalTaskRef)(taskId, 'output'),
|
|
49
|
+
raw_output_sha256: evidence.sha256,
|
|
50
|
+
raw_output_byte_length: evidence.byteLength,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function createTaskProjection(writer, taskId, provider, adapter, evidence) {
|
|
54
|
+
return {
|
|
55
|
+
writer,
|
|
56
|
+
taskId,
|
|
57
|
+
provider,
|
|
58
|
+
adapter,
|
|
59
|
+
promptRef: (0, trace_evidence_1.logicalTaskRef)(taskId, 'prompt'),
|
|
60
|
+
rawOutputRef: (0, trace_evidence_1.logicalTaskRef)(taskId, 'output'),
|
|
61
|
+
rawOutputSha256: evidence.sha256,
|
|
62
|
+
eventIndex: 0,
|
|
63
|
+
diagnosticIndex: 0,
|
|
64
|
+
diagnosticsOmitted: 0,
|
|
65
|
+
issueCodes: new Set(),
|
|
66
|
+
logFormat: provider === 'pi' ? 'legacy-pi' : null,
|
|
67
|
+
terminalResultEmitted: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function reportInitialIssues(context, options) {
|
|
71
|
+
if (options.agentIds.length > 1)
|
|
72
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'ambiguous_agent', null);
|
|
73
|
+
if (options.taskIssue) {
|
|
74
|
+
(0, semantic_contract_1.reportDiagnostic)(context, options.taskIssue.split(':').at(-1) || 'task_row_unreadable', null);
|
|
75
|
+
}
|
|
76
|
+
if (options.taskPresent && !options.taskTerminal) {
|
|
77
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'task_not_terminal', null);
|
|
78
|
+
}
|
|
79
|
+
if (options.evidenceIssue)
|
|
80
|
+
(0, semantic_contract_1.reportDiagnostic)(context, options.evidenceIssue, null);
|
|
81
|
+
if (options.taskPresent && !context.adapter)
|
|
82
|
+
(0, semantic_contract_1.reportDiagnostic)(context, 'unknown_provider', null);
|
|
83
|
+
}
|
|
84
|
+
function streamSemanticTask(options) {
|
|
85
|
+
const { writer, taskId, cause, readTask, allowedLogRoot, footerIssues } = options;
|
|
86
|
+
const taskRead = (0, trace_evidence_1.readTraceTask)(taskId, readTask);
|
|
87
|
+
const task = taskRead.task;
|
|
88
|
+
const provider = (0, export_stream_1.nullableString)(task?.provider);
|
|
89
|
+
const adapter = resolveAdapter(provider);
|
|
90
|
+
const evidence = task ? (0, semantic_evidence_1.openEvidence)(taskId, task, allowedLogRoot) : (0, semantic_evidence_1.emptyEvidence)();
|
|
91
|
+
const agentIds = [...cause.agentIds].sort(export_stream_1.compareText);
|
|
92
|
+
const taskTerminal = task !== null && (0, trace_evidence_1.hasTerminalTaskStatus)(task);
|
|
93
|
+
writeTaskRecord({
|
|
94
|
+
writer,
|
|
95
|
+
taskId,
|
|
96
|
+
agentId: agentIds.length === 1 ? (agentIds[0] ?? null) : null,
|
|
97
|
+
task,
|
|
98
|
+
adapter,
|
|
99
|
+
evidence,
|
|
100
|
+
});
|
|
101
|
+
const context = createTaskProjection(writer, taskId, provider, adapter, evidence);
|
|
102
|
+
reportInitialIssues(context, {
|
|
103
|
+
agentIds,
|
|
104
|
+
taskIssue: taskRead.issue,
|
|
105
|
+
taskPresent: task !== null,
|
|
106
|
+
taskTerminal,
|
|
107
|
+
evidenceIssue: evidence.issue,
|
|
108
|
+
});
|
|
109
|
+
let sourceStable = evidence.issue === null && evidence.fd !== null;
|
|
110
|
+
if (sourceStable && adapter)
|
|
111
|
+
sourceStable = (0, semantic_parser_1.parseOpenedEvidence)(evidence, context, adapter);
|
|
112
|
+
if (evidence.fd !== null)
|
|
113
|
+
fs.closeSync(evidence.fd);
|
|
114
|
+
const sourceComplete = sourceStable && taskTerminal;
|
|
115
|
+
const semanticComplete = sourceComplete && adapter !== null && context.issueCodes.size === 0;
|
|
116
|
+
for (const code of context.issueCodes)
|
|
117
|
+
footerIssues.push(`task:${taskId}:${code}`);
|
|
118
|
+
writer.write({
|
|
119
|
+
record_type: 'task_end',
|
|
120
|
+
task_id: taskId,
|
|
121
|
+
source_complete: sourceComplete,
|
|
122
|
+
semantic_complete: semanticComplete,
|
|
123
|
+
events: context.eventIndex,
|
|
124
|
+
diagnostics: context.diagnosticIndex,
|
|
125
|
+
diagnostics_omitted: context.diagnosticsOmitted,
|
|
126
|
+
});
|
|
127
|
+
return { events: context.eventIndex, diagnostics: context.diagnosticIndex };
|
|
128
|
+
}
|
|
129
|
+
function streamClusterSemanticExport(options) {
|
|
130
|
+
const { ledger, clusterId, readTask, allowedLogRoot, outputPath = null } = options;
|
|
131
|
+
const causes = (0, trace_evidence_1.collectTaskCauses)(ledger, clusterId);
|
|
132
|
+
assertOutputIsNotTaskLog(outputPath, allowedLogRoot, causes.keys());
|
|
133
|
+
const destination = (0, export_stream_1.createExclusiveDestination)(outputPath, options.stdout ?? process.stdout, 'Semantic');
|
|
134
|
+
const writer = (0, export_stream_1.createRecordWriter)(destination);
|
|
135
|
+
const footerIssues = [];
|
|
136
|
+
let events = 0;
|
|
137
|
+
let diagnostics = 0;
|
|
138
|
+
try {
|
|
139
|
+
writer.write({
|
|
140
|
+
record_type: 'header',
|
|
141
|
+
schema_version: semantic_contract_1.SEMANTIC_SCHEMA_VERSION,
|
|
142
|
+
media_type: semantic_contract_1.SEMANTIC_MEDIA_TYPE,
|
|
143
|
+
cluster_id: clusterId,
|
|
144
|
+
bounds: {
|
|
145
|
+
read_chunk_bytes: trace_output_1.TRACE_OUTPUT_CHUNK_BYTES,
|
|
146
|
+
max_line_bytes: semantic_contract_1.SEMANTIC_BOUNDS.maxLineBytes,
|
|
147
|
+
max_event_bytes: semantic_contract_1.SEMANTIC_BOUNDS.maxEventBytes,
|
|
148
|
+
max_string_bytes: semantic_contract_1.SEMANTIC_BOUNDS.maxStringBytes,
|
|
149
|
+
max_value_depth: semantic_contract_1.SEMANTIC_BOUNDS.maxValueDepth,
|
|
150
|
+
max_container_items: semantic_contract_1.SEMANTIC_BOUNDS.maxContainerItems,
|
|
151
|
+
max_value_nodes: semantic_contract_1.SEMANTIC_BOUNDS.maxValueNodes,
|
|
152
|
+
max_events_per_task: semantic_contract_1.SEMANTIC_BOUNDS.maxEventsPerTask,
|
|
153
|
+
max_diagnostics_per_task: semantic_contract_1.SEMANTIC_BOUNDS.maxDiagnosticsPerTask,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
const ordered = [...causes.entries()].sort(([left], [right]) => (0, export_stream_1.compareText)(left, right));
|
|
157
|
+
for (const [taskId, cause] of ordered) {
|
|
158
|
+
const summary = streamSemanticTask({
|
|
159
|
+
writer,
|
|
160
|
+
taskId,
|
|
161
|
+
cause,
|
|
162
|
+
readTask,
|
|
163
|
+
allowedLogRoot,
|
|
164
|
+
footerIssues,
|
|
165
|
+
});
|
|
166
|
+
events += summary.events;
|
|
167
|
+
diagnostics += summary.diagnostics;
|
|
168
|
+
}
|
|
169
|
+
footerIssues.sort(export_stream_1.compareText);
|
|
170
|
+
writer.finish({
|
|
171
|
+
record_type: 'footer',
|
|
172
|
+
complete: footerIssues.length === 0,
|
|
173
|
+
tasks: causes.size,
|
|
174
|
+
events,
|
|
175
|
+
diagnostics,
|
|
176
|
+
issues: footerIssues,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
destination.close();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
module.exports = {
|
|
184
|
+
SEMANTIC_EXPORT_BOUNDS: semantic_contract_1.SEMANTIC_BOUNDS,
|
|
185
|
+
streamClusterSemanticExport,
|
|
186
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs = require('fs');
|
|
2
|
+
import path = require('path');
|
|
3
|
+
import { getProviderAdapter, type ProviderAdapter } from './agent-provider-boundary';
|
|
4
|
+
import {
|
|
5
|
+
compareText,
|
|
6
|
+
createExclusiveDestination,
|
|
7
|
+
createRecordWriter,
|
|
8
|
+
nullableString,
|
|
9
|
+
type RecordWriter,
|
|
10
|
+
} from './export-stream';
|
|
11
|
+
import {
|
|
12
|
+
reportDiagnostic,
|
|
13
|
+
SEMANTIC_BOUNDS,
|
|
14
|
+
SEMANTIC_MEDIA_TYPE,
|
|
15
|
+
SEMANTIC_SCHEMA_VERSION,
|
|
16
|
+
type OpenedEvidence,
|
|
17
|
+
type SemanticExportOptions,
|
|
18
|
+
type SemanticTask,
|
|
19
|
+
type TaskProjection,
|
|
20
|
+
} from './semantic-contract';
|
|
21
|
+
import { emptyEvidence, openEvidence } from './semantic-evidence';
|
|
22
|
+
import { parseOpenedEvidence } from './semantic-parser';
|
|
23
|
+
import {
|
|
24
|
+
collectTaskCauses,
|
|
25
|
+
expectedLogPath,
|
|
26
|
+
hasTerminalTaskStatus,
|
|
27
|
+
logicalTaskRef,
|
|
28
|
+
readTraceTask,
|
|
29
|
+
type TaskCause,
|
|
30
|
+
} from './trace-evidence';
|
|
31
|
+
import { TRACE_OUTPUT_CHUNK_BYTES } from './trace-output';
|
|
32
|
+
|
|
33
|
+
interface StreamTaskOptions {
|
|
34
|
+
writer: RecordWriter;
|
|
35
|
+
taskId: string;
|
|
36
|
+
cause: TaskCause;
|
|
37
|
+
readTask: SemanticExportOptions['readTask'];
|
|
38
|
+
allowedLogRoot: string;
|
|
39
|
+
footerIssues: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface TaskRecordOptions {
|
|
43
|
+
writer: RecordWriter;
|
|
44
|
+
taskId: string;
|
|
45
|
+
agentId: string | null;
|
|
46
|
+
task: SemanticTask | null;
|
|
47
|
+
adapter: ProviderAdapter | null;
|
|
48
|
+
evidence: OpenedEvidence;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface InitialIssueOptions {
|
|
52
|
+
agentIds: readonly string[];
|
|
53
|
+
taskIssue: string | null;
|
|
54
|
+
taskPresent: boolean;
|
|
55
|
+
taskTerminal: boolean;
|
|
56
|
+
evidenceIssue: string | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assertOutputIsNotTaskLog(
|
|
60
|
+
outputPath: string | null | undefined,
|
|
61
|
+
allowedLogRoot: string,
|
|
62
|
+
taskIds: Iterable<string>
|
|
63
|
+
): void {
|
|
64
|
+
if (!outputPath) return;
|
|
65
|
+
const resolvedOutput = path.resolve(outputPath);
|
|
66
|
+
for (const taskId of taskIds) {
|
|
67
|
+
if (expectedLogPath(allowedLogRoot, taskId) === resolvedOutput) {
|
|
68
|
+
throw new Error('Semantic export output cannot replace a source task log');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveAdapter(provider: string | null): ProviderAdapter | null {
|
|
74
|
+
if (!provider) return null;
|
|
75
|
+
try {
|
|
76
|
+
return getProviderAdapter(provider);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function taskPrompt(task: SemanticTask | null): string | null {
|
|
83
|
+
if (!task) return null;
|
|
84
|
+
return nullableString(task.fullPrompt) ?? nullableString(task.prompt);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writeTaskRecord(options: TaskRecordOptions): void {
|
|
88
|
+
const { writer, taskId, agentId, task, adapter, evidence } = options;
|
|
89
|
+
writer.write({
|
|
90
|
+
record_type: 'task',
|
|
91
|
+
task_id: taskId,
|
|
92
|
+
agent_id: agentId,
|
|
93
|
+
provider: nullableString(task?.provider),
|
|
94
|
+
adapter_id: adapter?.id ?? null,
|
|
95
|
+
adapter_version: adapter?.adapterVersion ?? null,
|
|
96
|
+
model: nullableString(task?.model),
|
|
97
|
+
prompt_ref: logicalTaskRef(taskId, 'prompt'),
|
|
98
|
+
prompt: taskPrompt(task),
|
|
99
|
+
raw_output_ref: logicalTaskRef(taskId, 'output'),
|
|
100
|
+
raw_output_sha256: evidence.sha256,
|
|
101
|
+
raw_output_byte_length: evidence.byteLength,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createTaskProjection(
|
|
106
|
+
writer: RecordWriter,
|
|
107
|
+
taskId: string,
|
|
108
|
+
provider: string | null,
|
|
109
|
+
adapter: ProviderAdapter | null,
|
|
110
|
+
evidence: OpenedEvidence
|
|
111
|
+
): TaskProjection {
|
|
112
|
+
return {
|
|
113
|
+
writer,
|
|
114
|
+
taskId,
|
|
115
|
+
provider,
|
|
116
|
+
adapter,
|
|
117
|
+
promptRef: logicalTaskRef(taskId, 'prompt'),
|
|
118
|
+
rawOutputRef: logicalTaskRef(taskId, 'output'),
|
|
119
|
+
rawOutputSha256: evidence.sha256,
|
|
120
|
+
eventIndex: 0,
|
|
121
|
+
diagnosticIndex: 0,
|
|
122
|
+
diagnosticsOmitted: 0,
|
|
123
|
+
issueCodes: new Set<string>(),
|
|
124
|
+
logFormat: provider === 'pi' ? 'legacy-pi' : null,
|
|
125
|
+
terminalResultEmitted: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function reportInitialIssues(context: TaskProjection, options: InitialIssueOptions): void {
|
|
130
|
+
if (options.agentIds.length > 1) reportDiagnostic(context, 'ambiguous_agent', null);
|
|
131
|
+
if (options.taskIssue) {
|
|
132
|
+
reportDiagnostic(context, options.taskIssue.split(':').at(-1) || 'task_row_unreadable', null);
|
|
133
|
+
}
|
|
134
|
+
if (options.taskPresent && !options.taskTerminal) {
|
|
135
|
+
reportDiagnostic(context, 'task_not_terminal', null);
|
|
136
|
+
}
|
|
137
|
+
if (options.evidenceIssue) reportDiagnostic(context, options.evidenceIssue, null);
|
|
138
|
+
if (options.taskPresent && !context.adapter) reportDiagnostic(context, 'unknown_provider', null);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function streamSemanticTask(options: StreamTaskOptions): { events: number; diagnostics: number } {
|
|
142
|
+
const { writer, taskId, cause, readTask, allowedLogRoot, footerIssues } = options;
|
|
143
|
+
const taskRead = readTraceTask(taskId, readTask);
|
|
144
|
+
const task = taskRead.task as SemanticTask | null;
|
|
145
|
+
const provider = nullableString(task?.provider);
|
|
146
|
+
const adapter = resolveAdapter(provider);
|
|
147
|
+
const evidence = task ? openEvidence(taskId, task, allowedLogRoot) : emptyEvidence();
|
|
148
|
+
const agentIds = [...cause.agentIds].sort(compareText);
|
|
149
|
+
const taskTerminal = task !== null && hasTerminalTaskStatus(task);
|
|
150
|
+
writeTaskRecord({
|
|
151
|
+
writer,
|
|
152
|
+
taskId,
|
|
153
|
+
agentId: agentIds.length === 1 ? (agentIds[0] ?? null) : null,
|
|
154
|
+
task,
|
|
155
|
+
adapter,
|
|
156
|
+
evidence,
|
|
157
|
+
});
|
|
158
|
+
const context = createTaskProjection(writer, taskId, provider, adapter, evidence);
|
|
159
|
+
reportInitialIssues(context, {
|
|
160
|
+
agentIds,
|
|
161
|
+
taskIssue: taskRead.issue,
|
|
162
|
+
taskPresent: task !== null,
|
|
163
|
+
taskTerminal,
|
|
164
|
+
evidenceIssue: evidence.issue,
|
|
165
|
+
});
|
|
166
|
+
let sourceStable = evidence.issue === null && evidence.fd !== null;
|
|
167
|
+
if (sourceStable && adapter) sourceStable = parseOpenedEvidence(evidence, context, adapter);
|
|
168
|
+
if (evidence.fd !== null) fs.closeSync(evidence.fd);
|
|
169
|
+
const sourceComplete = sourceStable && taskTerminal;
|
|
170
|
+
const semanticComplete = sourceComplete && adapter !== null && context.issueCodes.size === 0;
|
|
171
|
+
for (const code of context.issueCodes) footerIssues.push(`task:${taskId}:${code}`);
|
|
172
|
+
writer.write({
|
|
173
|
+
record_type: 'task_end',
|
|
174
|
+
task_id: taskId,
|
|
175
|
+
source_complete: sourceComplete,
|
|
176
|
+
semantic_complete: semanticComplete,
|
|
177
|
+
events: context.eventIndex,
|
|
178
|
+
diagnostics: context.diagnosticIndex,
|
|
179
|
+
diagnostics_omitted: context.diagnosticsOmitted,
|
|
180
|
+
});
|
|
181
|
+
return { events: context.eventIndex, diagnostics: context.diagnosticIndex };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function streamClusterSemanticExport(options: SemanticExportOptions): void {
|
|
185
|
+
const { ledger, clusterId, readTask, allowedLogRoot, outputPath = null } = options;
|
|
186
|
+
const causes = collectTaskCauses(ledger, clusterId);
|
|
187
|
+
assertOutputIsNotTaskLog(outputPath, allowedLogRoot, causes.keys());
|
|
188
|
+
const destination = createExclusiveDestination(
|
|
189
|
+
outputPath,
|
|
190
|
+
options.stdout ?? process.stdout,
|
|
191
|
+
'Semantic'
|
|
192
|
+
);
|
|
193
|
+
const writer = createRecordWriter(destination);
|
|
194
|
+
const footerIssues: string[] = [];
|
|
195
|
+
let events = 0;
|
|
196
|
+
let diagnostics = 0;
|
|
197
|
+
try {
|
|
198
|
+
writer.write({
|
|
199
|
+
record_type: 'header',
|
|
200
|
+
schema_version: SEMANTIC_SCHEMA_VERSION,
|
|
201
|
+
media_type: SEMANTIC_MEDIA_TYPE,
|
|
202
|
+
cluster_id: clusterId,
|
|
203
|
+
bounds: {
|
|
204
|
+
read_chunk_bytes: TRACE_OUTPUT_CHUNK_BYTES,
|
|
205
|
+
max_line_bytes: SEMANTIC_BOUNDS.maxLineBytes,
|
|
206
|
+
max_event_bytes: SEMANTIC_BOUNDS.maxEventBytes,
|
|
207
|
+
max_string_bytes: SEMANTIC_BOUNDS.maxStringBytes,
|
|
208
|
+
max_value_depth: SEMANTIC_BOUNDS.maxValueDepth,
|
|
209
|
+
max_container_items: SEMANTIC_BOUNDS.maxContainerItems,
|
|
210
|
+
max_value_nodes: SEMANTIC_BOUNDS.maxValueNodes,
|
|
211
|
+
max_events_per_task: SEMANTIC_BOUNDS.maxEventsPerTask,
|
|
212
|
+
max_diagnostics_per_task: SEMANTIC_BOUNDS.maxDiagnosticsPerTask,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
const ordered = [...causes.entries()].sort(([left], [right]) => compareText(left, right));
|
|
216
|
+
for (const [taskId, cause] of ordered) {
|
|
217
|
+
const summary = streamSemanticTask({
|
|
218
|
+
writer,
|
|
219
|
+
taskId,
|
|
220
|
+
cause,
|
|
221
|
+
readTask,
|
|
222
|
+
allowedLogRoot,
|
|
223
|
+
footerIssues,
|
|
224
|
+
});
|
|
225
|
+
events += summary.events;
|
|
226
|
+
diagnostics += summary.diagnostics;
|
|
227
|
+
}
|
|
228
|
+
footerIssues.sort(compareText);
|
|
229
|
+
writer.finish({
|
|
230
|
+
record_type: 'footer',
|
|
231
|
+
complete: footerIssues.length === 0,
|
|
232
|
+
tasks: causes.size,
|
|
233
|
+
events,
|
|
234
|
+
diagnostics,
|
|
235
|
+
issues: footerIssues,
|
|
236
|
+
});
|
|
237
|
+
} finally {
|
|
238
|
+
destination.close();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export = {
|
|
243
|
+
SEMANTIC_EXPORT_BOUNDS: SEMANTIC_BOUNDS,
|
|
244
|
+
streamClusterSemanticExport,
|
|
245
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SemanticProjectionError = void 0;
|
|
4
|
+
exports.normalizeUnknown = normalizeUnknown;
|
|
5
|
+
const export_stream_1 = require("./export-stream");
|
|
6
|
+
const semantic_contract_1 = require("./semantic-contract");
|
|
7
|
+
class SemanticProjectionError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code) {
|
|
10
|
+
super(code);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.SemanticProjectionError = SemanticProjectionError;
|
|
15
|
+
function isPlainRecord(value) {
|
|
16
|
+
const prototype = Reflect.getPrototypeOf(value);
|
|
17
|
+
return prototype === Object.prototype || prototype === null;
|
|
18
|
+
}
|
|
19
|
+
function normalizeJsonValue(value, depth, budget) {
|
|
20
|
+
budget.nodes += 1;
|
|
21
|
+
if (budget.nodes > semantic_contract_1.MAX_VALUE_NODES)
|
|
22
|
+
throw new SemanticProjectionError('event_shape_too_large');
|
|
23
|
+
if (value === null || value === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
if (Buffer.byteLength(value) > semantic_contract_1.MAX_STRING_BYTES) {
|
|
27
|
+
throw new SemanticProjectionError('event_string_too_large');
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'boolean')
|
|
32
|
+
return value;
|
|
33
|
+
if (typeof value === 'number') {
|
|
34
|
+
if (!Number.isFinite(value))
|
|
35
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
36
|
+
return Object.is(value, -0) ? 0 : value;
|
|
37
|
+
}
|
|
38
|
+
if (typeof value !== 'object')
|
|
39
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
40
|
+
if (depth >= semantic_contract_1.MAX_VALUE_DEPTH)
|
|
41
|
+
throw new SemanticProjectionError('event_shape_too_deep');
|
|
42
|
+
if (budget.seen.has(value))
|
|
43
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
44
|
+
budget.seen.add(value);
|
|
45
|
+
try {
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
if (value.length > semantic_contract_1.MAX_CONTAINER_ITEMS) {
|
|
48
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
49
|
+
}
|
|
50
|
+
return value.map((item) => normalizeJsonValue(item, depth + 1, budget));
|
|
51
|
+
}
|
|
52
|
+
if (!isPlainRecord(value))
|
|
53
|
+
throw new SemanticProjectionError('event_value_not_json');
|
|
54
|
+
const keys = Object.keys(value).sort(export_stream_1.compareText);
|
|
55
|
+
if (keys.length > semantic_contract_1.MAX_CONTAINER_ITEMS) {
|
|
56
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
57
|
+
}
|
|
58
|
+
const normalized = {};
|
|
59
|
+
for (const key of keys)
|
|
60
|
+
normalized[key] = normalizeJsonValue(value[key], depth + 1, budget);
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
budget.seen.delete(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function normalizeUnknown(value) {
|
|
68
|
+
return normalizeJsonValue(value, 0, { nodes: 0, seen: new WeakSet() });
|
|
69
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { compareText } from './export-stream';
|
|
2
|
+
import {
|
|
3
|
+
MAX_CONTAINER_ITEMS,
|
|
4
|
+
MAX_STRING_BYTES,
|
|
5
|
+
MAX_VALUE_DEPTH,
|
|
6
|
+
MAX_VALUE_NODES,
|
|
7
|
+
} from './semantic-contract';
|
|
8
|
+
|
|
9
|
+
interface JsonBudget {
|
|
10
|
+
nodes: number;
|
|
11
|
+
seen: WeakSet<object>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class SemanticProjectionError extends Error {
|
|
15
|
+
readonly code: string;
|
|
16
|
+
|
|
17
|
+
constructor(code: string) {
|
|
18
|
+
super(code);
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isPlainRecord(value: object): value is Record<string, unknown> {
|
|
24
|
+
const prototype: object | null = Reflect.getPrototypeOf(value);
|
|
25
|
+
return prototype === Object.prototype || prototype === null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeJsonValue(value: unknown, depth: number, budget: JsonBudget): unknown {
|
|
29
|
+
budget.nodes += 1;
|
|
30
|
+
if (budget.nodes > MAX_VALUE_NODES) throw new SemanticProjectionError('event_shape_too_large');
|
|
31
|
+
if (value === null || value === undefined) return null;
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
if (Buffer.byteLength(value) > MAX_STRING_BYTES) {
|
|
34
|
+
throw new SemanticProjectionError('event_string_too_large');
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === 'boolean') return value;
|
|
39
|
+
if (typeof value === 'number') {
|
|
40
|
+
if (!Number.isFinite(value)) throw new SemanticProjectionError('event_value_not_json');
|
|
41
|
+
return Object.is(value, -0) ? 0 : value;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value !== 'object') throw new SemanticProjectionError('event_value_not_json');
|
|
44
|
+
if (depth >= MAX_VALUE_DEPTH) throw new SemanticProjectionError('event_shape_too_deep');
|
|
45
|
+
if (budget.seen.has(value)) throw new SemanticProjectionError('event_value_not_json');
|
|
46
|
+
budget.seen.add(value);
|
|
47
|
+
try {
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length > MAX_CONTAINER_ITEMS) {
|
|
50
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
51
|
+
}
|
|
52
|
+
return value.map((item) => normalizeJsonValue(item, depth + 1, budget));
|
|
53
|
+
}
|
|
54
|
+
if (!isPlainRecord(value)) throw new SemanticProjectionError('event_value_not_json');
|
|
55
|
+
const keys = Object.keys(value).sort(compareText);
|
|
56
|
+
if (keys.length > MAX_CONTAINER_ITEMS) {
|
|
57
|
+
throw new SemanticProjectionError('event_shape_too_wide');
|
|
58
|
+
}
|
|
59
|
+
const normalized: Record<string, unknown> = {};
|
|
60
|
+
for (const key of keys) normalized[key] = normalizeJsonValue(value[key], depth + 1, budget);
|
|
61
|
+
return normalized;
|
|
62
|
+
} finally {
|
|
63
|
+
budget.seen.delete(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function normalizeUnknown(value: unknown): unknown {
|
|
68
|
+
return normalizeJsonValue(value, 0, { nodes: 0, seen: new WeakSet<object>() });
|
|
69
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BoundedLineScanner = void 0;
|
|
4
|
+
const semantic_contract_1 = require("./semantic-contract");
|
|
5
|
+
class BoundedLineScanner {
|
|
6
|
+
consumeLine;
|
|
7
|
+
parts = [];
|
|
8
|
+
lineBytes = 0;
|
|
9
|
+
lineStart = 0;
|
|
10
|
+
lineNumber = 1;
|
|
11
|
+
oversized = false;
|
|
12
|
+
constructor(consumeLine) {
|
|
13
|
+
this.consumeLine = consumeLine;
|
|
14
|
+
}
|
|
15
|
+
consume(chunk, absoluteStart) {
|
|
16
|
+
let offset = 0;
|
|
17
|
+
while (offset < chunk.length) {
|
|
18
|
+
const newline = chunk.indexOf(0x0a, offset);
|
|
19
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
20
|
+
this.append(chunk.subarray(offset, end));
|
|
21
|
+
if (newline === -1)
|
|
22
|
+
return;
|
|
23
|
+
this.flush(absoluteStart + newline);
|
|
24
|
+
offset = newline + 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
finish(byteLength) {
|
|
28
|
+
if (this.lineBytes > 0 || this.oversized)
|
|
29
|
+
this.flush(byteLength);
|
|
30
|
+
}
|
|
31
|
+
append(part) {
|
|
32
|
+
this.lineBytes += part.length;
|
|
33
|
+
if (this.lineBytes > semantic_contract_1.MAX_LINE_BYTES) {
|
|
34
|
+
this.oversized = true;
|
|
35
|
+
this.parts.length = 0;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!this.oversized && part.length > 0)
|
|
39
|
+
this.parts.push(part);
|
|
40
|
+
}
|
|
41
|
+
flush(byteEnd) {
|
|
42
|
+
const source = {
|
|
43
|
+
line_number: this.lineNumber,
|
|
44
|
+
byte_start: this.lineStart,
|
|
45
|
+
byte_end: byteEnd,
|
|
46
|
+
timestamp_ms: null,
|
|
47
|
+
};
|
|
48
|
+
this.consumeLine(this.oversized ? null : Buffer.concat(this.parts, this.lineBytes), source);
|
|
49
|
+
this.parts.length = 0;
|
|
50
|
+
this.lineBytes = 0;
|
|
51
|
+
this.lineStart = byteEnd + 1;
|
|
52
|
+
this.lineNumber += 1;
|
|
53
|
+
this.oversized = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.BoundedLineScanner = BoundedLineScanner;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MAX_LINE_BYTES, type SourceSpan } from './semantic-contract';
|
|
2
|
+
|
|
3
|
+
type LineConsumer = (line: Buffer | null, source: SourceSpan) => void;
|
|
4
|
+
|
|
5
|
+
export class BoundedLineScanner {
|
|
6
|
+
private readonly parts: Buffer[] = [];
|
|
7
|
+
private lineBytes = 0;
|
|
8
|
+
private lineStart = 0;
|
|
9
|
+
private lineNumber = 1;
|
|
10
|
+
private oversized = false;
|
|
11
|
+
|
|
12
|
+
constructor(private readonly consumeLine: LineConsumer) {}
|
|
13
|
+
|
|
14
|
+
consume(chunk: Buffer, absoluteStart: number): void {
|
|
15
|
+
let offset = 0;
|
|
16
|
+
while (offset < chunk.length) {
|
|
17
|
+
const newline = chunk.indexOf(0x0a, offset);
|
|
18
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
19
|
+
this.append(chunk.subarray(offset, end));
|
|
20
|
+
if (newline === -1) return;
|
|
21
|
+
this.flush(absoluteStart + newline);
|
|
22
|
+
offset = newline + 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
finish(byteLength: number): void {
|
|
27
|
+
if (this.lineBytes > 0 || this.oversized) this.flush(byteLength);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private append(part: Buffer): void {
|
|
31
|
+
this.lineBytes += part.length;
|
|
32
|
+
if (this.lineBytes > MAX_LINE_BYTES) {
|
|
33
|
+
this.oversized = true;
|
|
34
|
+
this.parts.length = 0;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!this.oversized && part.length > 0) this.parts.push(part);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private flush(byteEnd: number): void {
|
|
41
|
+
const source: SourceSpan = {
|
|
42
|
+
line_number: this.lineNumber,
|
|
43
|
+
byte_start: this.lineStart,
|
|
44
|
+
byte_end: byteEnd,
|
|
45
|
+
timestamp_ms: null,
|
|
46
|
+
};
|
|
47
|
+
this.consumeLine(this.oversized ? null : Buffer.concat(this.parts, this.lineBytes), source);
|
|
48
|
+
this.parts.length = 0;
|
|
49
|
+
this.lineBytes = 0;
|
|
50
|
+
this.lineStart = byteEnd + 1;
|
|
51
|
+
this.lineNumber += 1;
|
|
52
|
+
this.oversized = false;
|
|
53
|
+
}
|
|
54
|
+
}
|