@bjoernrochel/sevro 0.1.0-rc.16 → 0.1.0-rc.17

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.
@@ -23,10 +23,10 @@ Local installation records the selected version in the project lockfile.
23
23
 
24
24
  ## Current release
25
25
 
26
- The current release is `0.1.0-rc.16`. Install this exact version:
26
+ The current release is `0.1.0-rc.17`. Install this exact version:
27
27
 
28
28
  ```sh
29
- bun add --exact @bjoernrochel/sevro@0.1.0-rc.16
29
+ bun add --exact @bjoernrochel/sevro@0.1.0-rc.17
30
30
  ```
31
31
 
32
32
  <!-- sevro-current-release:end -->
@@ -5,7 +5,8 @@ retain an observation with that ID. It exposes host facts for extension policy:
5
5
 
6
6
  - `method: native_control_calls`;
7
7
  - ordered `calls` containing only `ordinal`, `namespace`, and `name`;
8
- - `acceptedAgentCount`, a verified Codex acceptance count or `null` on Claude;
8
+ - `acceptedAgentCount`, a verified direct native Codex acceptance count when
9
+ overall coverage is complete, otherwise `null` (also `null` on Claude);
9
10
  - `submittedExecCalls`, the Codex submitted-code count or `null` on Claude; and
10
11
  - `truncated`, which is `false` for a complete record and may be unknown (`null`)
11
12
  for an incomplete Codex record.
@@ -16,6 +17,18 @@ invocation attempt, not success. Extensions decide which calls are prohibited.
16
17
  An unknown acceptance count cannot establish agent acceptance.
17
18
 
18
19
  Codex projects labels and counts from its bound native-session observation.
20
+ It additionally retains `directAcceptedAgentCount`, the number of positively
21
+ correlated direct native spawn acceptances in that source, independently of exec
22
+ inspection. This is an observed count, not a total across unknown launch paths.
23
+ `coverage.nativeCalls` records native-session completeness;
24
+ `coverage.execInspection` records whether all submitted exec sources were
25
+ understood and matched to the direct submission inventory;
26
+ `coverage.acceptedAgents: direct_native_calls` names the scope of both acceptance
27
+ counts. Accepted launches through exec are not included in these direct counts.
28
+ When native capture is incomplete, even a zero observed count cannot establish
29
+ absence. Native labels remain usable as positive attempt evidence regardless
30
+ of an unrelated exec inspection gap. A rejected attempt is never an acceptance.
31
+
19
32
  When submitted code is present, both observations also retain `execCalls`:
20
33
  one receipt per native `exec` submission, with its native `ordinal`, inspection
21
34
  `completeness`, and ordered `calls` containing `index`, `namespace`, and `name`.
@@ -27,25 +40,46 @@ programs without executing them. It accepts literal tool calls, constant result
27
40
  bindings, template interpolation and `+` composition of inspected expressions,
28
41
  direct `text`/`store` output, and literal `Promise.all`/`allSettled` batches.
29
42
  Batch results may use flat constant array bindings or result-only
30
- `forEach(text)` or `for ... of` output. Destructuring defaults, rest elements,
43
+ `forEach(text)` or `for ... of` output, including blocks and conditional
44
+ result-field selection. `JSON.stringify`, `String`, object spread/shorthand,
45
+ and nested result fields are supported. Numeric output loops must use the
46
+ bounded shape `for (let i = 0; i < results.length; i++)`, indexing the known
47
+ batch with that loop variable. Output loops and conditional expressions cannot
48
+ invoke tools. Destructuring defaults, rest elements,
31
49
  holes, nested bindings, duplicate names, and shadowed globals remain unsupported.
32
- It requires one
33
- unique, later native completion for the same call ID with an intact successful
34
- script envelope. Tool argument strings, comments and printed tool-looking text
50
+ Tool argument strings, comments and printed tool-looking text
35
51
  do not become call labels. A rejected tool request returned by a completed
36
52
  script still appears in its submitted-call inventory. This inventory proves
37
53
  submission of those requests, not successful effects or acceptance; the existing
38
54
  acceptance count describes independently correlated direct native spawns.
39
55
 
40
56
  Dynamic lookup, aliases, shadowed globals, callbacks that invoke tools,
41
- conditional code, nested executors, malformed or truncated results, duplicate
42
- receipts, and missing completion keep inspection partial. Source is bounded at
57
+ conditional tool execution, and nested executors keep source inspection partial.
58
+ Each receipt's `inspectionReason` is `null` on success, or `unsupported_source`,
59
+ `malformed_source`, `source_limit`, or `foreign_namespace`. Unsupported syntax
60
+ and expression/call-budget exhaustion use `unsupported_source`.
61
+ Source is bounded at
43
62
  64 KiB, traversed expressions at 2,048, and calls at 128 per submission; tool
44
63
  labels are bounded at 64 characters. Private source and output bytes are discarded.
45
64
  The portable observation becomes partial if any submission is uninspected or
46
- receipt counts/ordinals disagree with the native list. Incomplete counts remain
47
- `null`. Native-session completeness still describes session capture separately
48
- from each submission's inspection completeness.
65
+ receipt counts/ordinals disagree with the native list. `acceptedAgentCount`
66
+ remains `null` in that case, while native coverage, direct acceptance facts and
67
+ the native `submittedExecCalls` count remain independently available.
68
+ `submittedExecCalls` and `truncated` depend on native capture, not exec output.
69
+
70
+ Receipt `completeness` describes source inspection, independently of execution
71
+ or displayed output. `execution` is `completed`, `failed`, or `incomplete`;
72
+ `output` is `complete`, `truncated`, or `unavailable`. Output evidence requires
73
+ a unique later output bound to the same native call ID, with recognized text
74
+ blocks. `complete` describes the displayed receipt, not the whole program's
75
+ execution. Execution status additionally requires a successful or failed script
76
+ header. Missing, ambiguous or
77
+ malformed output yields `incomplete`/`unavailable`; a yielded or unrecognized
78
+ header stays `incomplete`. Displayed truncation does not change source coverage.
79
+ A fully inspected source with no launch call proves that absence even if its
80
+ shell output was truncated or the exec cell failed. Source inspection cannot
81
+ prove execution or success. Missing fields in historical receipts are unknown;
82
+ historical artifacts retain their original results and meanings.
49
83
 
50
84
  Claude records every tool-use label from the bounded stream, with namespace
51
85
  `claude`, without filtering to Skill or Agent. It requires a completed successful
@@ -55,11 +89,12 @@ Tool-looking text in arguments or output does not become a native call.
55
89
 
56
90
  Consumers must verify the source, completeness, shape, ordering, and counts
57
91
  before using an empty call list to establish absence. Missing or partial
58
- evidence cannot establish a negative assertion. For Codex, consumers must
92
+ source coverage cannot establish a negative assertion. For Codex, consumers must
59
93
  inspect every `execCalls` receipt in addition to the direct `calls` list,
60
94
  validate receipt ordinals against the direct `exec` submissions, and require
61
95
  all receipts to be complete with counts matching `submittedExecCalls`.
62
- They must examine submitted labels for prohibited controls even when
96
+ They can use positively observed native attempt labels before requiring complete
97
+ coverage for an absence pass. They must examine submitted labels for prohibited controls even when
63
98
  `acceptedAgentCount` is zero. Older consumers that require
64
99
  `submittedExecCalls === 0` remain conservative but cannot use this new evidence
65
100
  until they adopt receipt validation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bjoernrochel/sevro",
3
- "version": "0.1.0-rc.16",
3
+ "version": "0.1.0-rc.17",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "A standalone evaluation runner for agentic coding capabilities",
6
6
  "type": "module",
@@ -21,6 +21,14 @@ export interface ExecCallReceipt {
21
21
  ordinal: number;
22
22
  completeness: "complete" | "partial";
23
23
  calls: SubmittedCall[];
24
+ inspectionReason?:
25
+ | "unsupported_source"
26
+ | "malformed_source"
27
+ | "source_limit"
28
+ | "foreign_namespace"
29
+ | null;
30
+ execution?: "completed" | "failed" | "incomplete";
31
+ output?: "complete" | "truncated" | "unavailable";
24
32
  }
25
33
 
26
34
  function toolNamespace(name: string): SubmittedCall["namespace"] {
@@ -77,7 +85,15 @@ function member(node: ts.Expression, owner: string, names: string[]): boolean {
77
85
  function localName(node: ts.BindingName): string | undefined {
78
86
  const ts = typescript();
79
87
  if (!ts.isIdentifier(node)) return undefined;
80
- return ["tools", "text", "store", "Promise", "load"].includes(node.text)
88
+ return [
89
+ "tools",
90
+ "text",
91
+ "store",
92
+ "Promise",
93
+ "load",
94
+ "JSON",
95
+ "String",
96
+ ].includes(node.text)
81
97
  ? undefined
82
98
  : node.text;
83
99
  }
@@ -85,6 +101,7 @@ class Submission {
85
101
  readonly calls: SubmittedCall[] = [];
86
102
  private readonly locals = new Set<string>();
87
103
  private readonly collections = new Set<string>();
104
+ private readonly indices = new Set<string>();
88
105
  private nodes = 0;
89
106
  expression(node: ts.Expression): boolean {
90
107
  const ts = typescript();
@@ -110,17 +127,42 @@ class Submission {
110
127
  return this.localProperty(node);
111
128
  case ts.isCallExpression(node):
112
129
  return this.call(node);
130
+ case ts.isConditionalExpression(node):
131
+ return [node.condition, node.whenTrue, node.whenFalse].every((part) =>
132
+ this.outputExpression(part),
133
+ );
134
+ case ts.isElementAccessExpression(node):
135
+ return (
136
+ this.collection(node.expression) &&
137
+ typescript().isIdentifier(node.argumentExpression) &&
138
+ this.indices.has(node.argumentExpression.text)
139
+ );
113
140
  default:
114
141
  return false;
115
142
  }
116
143
  }
117
144
  private property(node: ts.ObjectLiteralElementLike): boolean {
118
145
  const ts = typescript();
146
+ if (ts.isSpreadAssignment(node))
147
+ return this.outputExpression(node.expression);
148
+ if (ts.isShorthandPropertyAssignment(node)) return this.shorthand(node);
119
149
  if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name))
120
150
  return false;
121
151
  return this.expression(node.initializer);
122
152
  }
153
+ private shorthand(node: ts.ShorthandPropertyAssignment): boolean {
154
+ return !node.objectAssignmentInitializer && this.expression(node.name);
155
+ }
123
156
  private composition(node: ts.BinaryExpression): boolean {
157
+ if (
158
+ [
159
+ typescript().SyntaxKind.EqualsEqualsEqualsToken,
160
+ typescript().SyntaxKind.ExclamationEqualsEqualsToken,
161
+ ].includes(node.operatorToken.kind)
162
+ )
163
+ return (
164
+ this.outputExpression(node.left) && this.outputExpression(node.right)
165
+ );
124
166
  return (
125
167
  node.operatorToken.kind === typescript().SyntaxKind.PlusToken &&
126
168
  this.expression(node.left) &&
@@ -148,13 +190,12 @@ class Submission {
148
190
  if (ts.isVariableStatement(node))
149
191
  return this.declarations(node.declarationList);
150
192
  if (ts.isForOfStatement(node)) return this.outputLoop(node);
193
+ if (ts.isForStatement(node)) return this.numericOutputLoop(node);
151
194
  return ts.isExpressionStatement(node) && this.expression(node.expression);
152
195
  }
153
196
  private localProperty(node: ts.PropertyAccessExpression): boolean {
154
- const ts = typescript();
155
197
  return (
156
- ts.isIdentifier(node.expression) &&
157
- this.locals.has(node.expression.text) &&
198
+ this.expression(node.expression) &&
158
199
  !["constructor", "__proto__", "prototype"].includes(node.name.text)
159
200
  );
160
201
  }
@@ -231,10 +272,43 @@ class Submission {
231
272
  if (!this.collection(node.expression)) return false;
232
273
  if (!ts.isVariableDeclarationList(node.initializer)) return false;
233
274
  const name = loopVariable(node.initializer);
275
+ if (!name || this.locals.has(name)) return false;
276
+ this.locals.add(name);
277
+ const valid = this.outputBody(node.statement);
278
+ this.locals.delete(name);
279
+ return valid;
280
+ }
281
+ private outputExpression(node: ts.Expression): boolean {
282
+ const count = this.calls.length;
283
+ return this.expression(node) && this.calls.length === count;
284
+ }
285
+ private numericOutputLoop(node: ts.ForStatement): boolean {
286
+ const name = numericLoopVariable(node);
287
+ if (!name || this.locals.has(name)) return false;
288
+ const condition = node.condition as ts.BinaryExpression;
289
+ const bound = condition.right as ts.PropertyAccessExpression;
290
+ if (!this.collection(bound.expression)) return false;
291
+ this.locals.add(name);
292
+ this.indices.add(name);
293
+ const valid = this.outputBody(node.statement);
294
+ this.locals.delete(name);
295
+ this.indices.delete(name);
296
+ return valid;
297
+ }
298
+ private outputBody(node: ts.Statement): boolean {
299
+ const ts = typescript();
300
+ if (ts.isBlock(node))
301
+ return node.statements.every((statement) => this.outputBody(statement));
302
+ if (ts.isIfStatement(node)) return this.outputConditional(node);
234
303
  return (
235
- name !== undefined &&
236
- !this.locals.has(name) &&
237
- outputLoopBody(node.statement, name)
304
+ ts.isExpressionStatement(node) && this.outputExpression(node.expression)
305
+ );
306
+ }
307
+ private outputConditional(node: ts.IfStatement): boolean {
308
+ return (
309
+ this.outputExpression(node.expression) &&
310
+ this.outputBody(node.thenStatement) &&
311
+ (!node.elseStatement || this.outputBody(node.elseStatement))
238
312
  );
239
313
  }
240
314
  private collection(node: ts.Expression): boolean {
@@ -244,9 +318,12 @@ class Submission {
244
318
  }
245
319
  function helperCall(node: ts.Expression): boolean {
246
320
  const ts = typescript();
247
- if (ts.isIdentifier(node) && ["text", "store"].includes(node.text))
321
+ if (ts.isIdentifier(node) && ["text", "store", "String"].includes(node.text))
248
322
  return true;
249
- return member(node, "Promise", ["all", "allSettled"]);
323
+ return (
324
+ member(node, "Promise", ["all", "allSettled"]) ||
325
+ member(node, "JSON", ["stringify"])
326
+ );
250
327
  }
251
328
  function namedIdentifier(
252
329
  node: ts.Expression | undefined,
@@ -271,22 +348,69 @@ function loopVariable(list: ts.VariableDeclarationList): string | undefined {
271
348
  ? localName(declaration.name)
272
349
  : undefined;
273
350
  }
274
- function outputLoopBody(statement: ts.Statement, name: string): boolean {
351
+ function numericLoopVariable(node: ts.ForStatement): string | undefined {
275
352
  const ts = typescript();
353
+ if (!node.initializer || !ts.isVariableDeclarationList(node.initializer))
354
+ return undefined;
355
+ const name = numericBinding(node.initializer);
356
+ return name && numericLoopBounds(node, name) ? name : undefined;
357
+ }
358
+ function numericBinding(list: ts.VariableDeclarationList): string | undefined {
276
359
  if (
277
- !ts.isExpressionStatement(statement) ||
278
- !ts.isCallExpression(statement.expression)
360
+ !(list.flags & typescript().NodeFlags.Let) ||
361
+ list.declarations.length !== 1
279
362
  )
280
- return false;
281
- const call = statement.expression;
363
+ return undefined;
364
+ const declaration = list.declarations[0];
365
+ return declaration && zeroInitializer(declaration.initializer)
366
+ ? localName(declaration.name)
367
+ : undefined;
368
+ }
369
+ function zeroInitializer(node: ts.Expression | undefined): boolean {
370
+ return (
371
+ node !== undefined &&
372
+ typescript().isNumericLiteral(node) &&
373
+ node.text === "0"
374
+ );
375
+ }
376
+ function numericLoopBounds(node: ts.ForStatement, name: string): boolean {
377
+ const ts = typescript();
378
+ const condition = node.condition;
379
+ if (!condition || !ts.isBinaryExpression(condition)) return false;
282
380
  return (
283
- namedIdentifier(call.expression, "text") &&
284
- call.arguments.length === 1 &&
285
- namedIdentifier(call.arguments[0], name)
381
+ numericCondition(condition, name) &&
382
+ numericIncrement(node.incrementor, name)
286
383
  );
287
384
  }
288
- function inspectedSource(code: unknown): Submission | undefined {
289
- if (typeof code !== "string" || code.length > 65536) return undefined;
385
+ function numericCondition(node: ts.BinaryExpression, name: string): boolean {
386
+ return (
387
+ node.operatorToken.kind === typescript().SyntaxKind.LessThanToken &&
388
+ namedIdentifier(node.left, name) &&
389
+ lengthProperty(node.right)
390
+ );
391
+ }
392
+ function lengthProperty(node: ts.Expression): boolean {
393
+ return (
394
+ typescript().isPropertyAccessExpression(node) && node.name.text === "length"
395
+ );
396
+ }
397
+ function numericIncrement(
398
+ node: ts.Expression | undefined,
399
+ name: string,
400
+ ): boolean {
401
+ return (
402
+ node !== undefined &&
403
+ typescript().isPostfixUnaryExpression(node) &&
404
+ node.operator === typescript().SyntaxKind.PlusPlusToken &&
405
+ namedIdentifier(node.operand, name)
406
+ );
407
+ }
408
+ function inspectedSource(code: unknown): {
409
+ submission?: Submission;
410
+ reason: ExecCallReceipt["inspectionReason"];
411
+ } {
412
+ if (typeof code !== "string") return { reason: "unsupported_source" };
413
+ if (code.length > 65536) return { reason: "source_limit" };
290
414
  const ts = typescript();
291
415
  const source = ts.createSourceFile(
292
416
  "submitted.js",
@@ -298,33 +422,26 @@ function inspectedSource(code: unknown): Submission | undefined {
298
422
  const diagnostics = (
299
423
  source as ts.SourceFile & { parseDiagnostics: readonly ts.Diagnostic[] }
300
424
  ).parseDiagnostics;
301
- if (diagnostics.length !== 0) return undefined;
425
+ if (diagnostics.length !== 0) return { reason: "malformed_source" };
302
426
  const submission = new Submission();
303
427
  return source.statements.every((statement) => submission.statement(statement))
304
- ? submission
305
- : undefined;
428
+ ? { submission, reason: null }
429
+ : { reason: "unsupported_source" };
306
430
  }
307
- function completedOutput(output: unknown): boolean {
308
- if (isUnknownArray(output)) return completedBlocks(output);
309
- if (typeof output !== "string") return false;
431
+ function outputBlocks(output: unknown): unknown[] {
432
+ if (isUnknownArray(output)) return output;
433
+ if (typeof output !== "string") return [];
310
434
  try {
311
435
  const blocks: unknown = JSON.parse(output);
312
- if (!isUnknownArray(blocks)) return false;
313
- return completedBlocks(blocks);
436
+ return isUnknownArray(blocks) ? blocks : [];
314
437
  } catch {
315
- return false;
438
+ return [];
316
439
  }
317
440
  }
318
- function completedBlocks(blocks: unknown[]): boolean {
319
- return blocks.every(completeTextBlock) && completedHeader(blocks[0]);
320
- }
321
441
  function completeTextBlock(block: unknown): boolean {
322
442
  if (!isRecord(block) || !["text", "input_text"].includes(String(block.type)))
323
443
  return false;
324
- return (
325
- typeof block.text === "string" &&
326
- !block.text.includes("Warning: truncated output")
327
- );
444
+ return typeof block.text === "string";
328
445
  }
329
446
  function completedHeader(block: unknown): boolean {
330
447
  return (
@@ -335,23 +452,35 @@ function completedHeader(block: unknown): boolean {
335
452
  )
336
453
  );
337
454
  }
338
- function completedSubmission(entry: Entry, entries: Entry[]): boolean {
339
- if (!nativeExecNamespace(entry.payload.namespace)) return false;
455
+ function submissionOutput(entry: Entry, entries: Entry[]): unknown[] {
340
456
  const id = entry.payload.call_id;
341
- if (typeof id !== "string") return false;
457
+ if (typeof id !== "string") return [];
342
458
  const matches = entries.filter((other) => other.payload.call_id === id);
343
- if (matches.length !== 2) return false;
459
+ if (matches.length !== 2) return [];
344
460
  const result = matches.find(
345
461
  (other) => other.payload.type === "custom_tool_call_output",
346
462
  );
347
- return laterCompletion(result, entry.ordinal);
463
+ return result && result.ordinal > entry.ordinal
464
+ ? outputBlocks(result.payload.output)
465
+ : [];
348
466
  }
349
- function laterCompletion(result: Entry | undefined, ordinal: number): boolean {
350
- return (
351
- result !== undefined &&
352
- result.ordinal > ordinal &&
353
- completedOutput(result.payload.output)
467
+ function outputEvidence(
468
+ blocks: unknown[],
469
+ ): Pick<ExecCallReceipt, "execution" | "output"> {
470
+ if (!blocks.length || !blocks.every(completeTextBlock))
471
+ return { execution: "incomplete", output: "unavailable" };
472
+ const header = blocks[0] as { text: string };
473
+ const execution = executionStatus(header);
474
+ const truncated = blocks.some((block) =>
475
+ (block as { text: string }).text.includes("Warning: truncated output"),
354
476
  );
477
+ return { execution, output: truncated ? "truncated" : "complete" };
478
+ }
479
+ function executionStatus(header: {
480
+ text: string;
481
+ }): ExecCallReceipt["execution"] {
482
+ if (completedHeader(header)) return "completed";
483
+ return /^Script failed\b/.test(header.text) ? "failed" : "incomplete";
355
484
  }
356
485
  function nativeExecNamespace(namespace: unknown): boolean {
357
486
  return namespace === undefined || namespace === "functions";
@@ -363,14 +492,25 @@ export function codexExecCallReceipts(entries: Entry[]): ExecCallReceipt[] {
363
492
  ({ payload }) =>
364
493
  payload.type === "custom_tool_call" && payload.name === "exec",
365
494
  )
366
- .map((entry) => {
367
- const submission = inspectedSource(entry.payload.input);
368
- const complete =
369
- submission !== undefined && completedSubmission(entry, entries);
370
- return {
371
- ordinal: entry.ordinal,
372
- completeness: complete ? "complete" : "partial",
373
- calls: submission?.calls ?? [],
374
- };
375
- });
495
+ .map((entry) => ({
496
+ ordinal: entry.ordinal,
497
+ ...inspectionEvidence(entry),
498
+ ...outputEvidence(submissionOutput(entry, entries)),
499
+ }));
500
+ }
501
+ function inspectionEvidence(
502
+ entry: Entry,
503
+ ): Pick<ExecCallReceipt, "completeness" | "calls" | "inspectionReason"> {
504
+ if (!nativeExecNamespace(entry.payload.namespace))
505
+ return {
506
+ completeness: "partial",
507
+ calls: [],
508
+ inspectionReason: "foreign_namespace",
509
+ };
510
+ const inspected = inspectedSource(entry.payload.input);
511
+ return {
512
+ completeness: inspected.submission ? "complete" : "partial",
513
+ calls: inspected.submission?.calls ?? [],
514
+ inspectionReason: inspected.reason,
515
+ };
376
516
  }
@@ -14,6 +14,12 @@ export interface NativeControlObservation {
14
14
  method: "native_control_calls";
15
15
  calls: Call[];
16
16
  acceptedAgentCount: number | null;
17
+ directAcceptedAgentCount?: number;
18
+ coverage?: {
19
+ nativeCalls: NativeControlObservation["completeness"];
20
+ execInspection: "complete" | "partial";
21
+ acceptedAgents: "direct_native_calls";
22
+ };
17
23
  submittedExecCalls: number | null;
18
24
  execCalls?: ExecCallReceipt[];
19
25
  truncated: boolean | null;
@@ -47,12 +53,28 @@ export function codexNativeControls(
47
53
  acceptedAgentCount: complete
48
54
  ? observation.data.acceptedSpawns.length
49
55
  : null,
50
- submittedExecCalls: complete ? observation.data.submittedExecCalls : null,
56
+ directAcceptedAgentCount: observation.data.acceptedSpawns.length,
57
+ coverage: {
58
+ nativeCalls: observation.completeness,
59
+ execInspection: inspectedExecCalls(observation)
60
+ ? "complete"
61
+ : "partial",
62
+ acceptedAgents: "direct_native_calls",
63
+ },
64
+ ...nativeCounts(observation),
51
65
  ...execEvidence(observation.data.execCalls),
52
- truncated: complete ? false : null,
53
66
  },
54
67
  };
55
68
  }
69
+ function nativeCounts(
70
+ observation: Awaited<ReturnType<typeof codexNativeCallObservation>>,
71
+ ) {
72
+ const complete = observation.completeness === "complete";
73
+ return {
74
+ submittedExecCalls: complete ? observation.data.submittedExecCalls : null,
75
+ truncated: complete ? false : null,
76
+ };
77
+ }
56
78
  function controlCompleteness(
57
79
  source: NativeControlObservation["completeness"],
58
80
  complete: boolean,