@bjoernrochel/sevro 0.1.0-rc.20 → 0.1.0-rc.22

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.
@@ -238,6 +238,15 @@ body exposure and successful Claude `Skill` calls.
238
238
  This replaces the retired Codex skill-read and Claude nested-skill observations.
239
239
  Explicit invocation receipts remain dispatch evidence and do not contribute to
240
240
  `observedSkills`; extensions must migrate activation checks to the common ID.
241
+ Codex additionally supplies per-skill `skillExposures` and independent
242
+ `collectionStatus` in that observation. Its optional
243
+ `sevro.host.skill-exposure-claims` capability provides complete evidence only
244
+ for listed positive full or partial body exposures, even when collection is
245
+ partial. Extensions must check the requested skill and acceptable exposure
246
+ status explicitly; this evidence cannot establish absence or primary selection.
247
+ See the [exposure contract](native-hosts.md#skill-activation-evidence) for scope,
248
+ unknown states and host limitations. Required aggregate activation evidence
249
+ still needs a `complete` observation as before.
241
250
  Host adapters can also supply bounded evidence artifacts. The engine persists
242
251
  their bytes outside the candidate workspace before `evaluate`, then passes
243
252
  file URLs and digests through the existing artifact references. A required host
@@ -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.20`. Install this exact version:
26
+ The current release is `0.1.0-rc.22`. Install this exact version:
27
27
 
28
28
  ```sh
29
- bun add --exact @bjoernrochel/sevro@0.1.0-rc.20
29
+ bun add --exact @bjoernrochel/sevro@0.1.0-rc.22
30
30
  ```
31
31
 
32
32
  <!-- sevro-current-release:end -->
@@ -90,8 +90,55 @@ Newly created or changed skill bodies are outside the prepared snapshot. A
90
90
  complete empty observation is limited to this scope and the supported local
91
91
  tool hook paths.
92
92
 
93
+ Codex separates per-skill exposure from collection health in the same
94
+ observation. `skillExposures` contains one `{ skill, status }` entry per prepared
95
+ catalog entry, in catalog order, with qualified skill names:
96
+
97
+ | Status | Meaning |
98
+ | ----------- | ----------------------------------------------------------------------- |
99
+ | `full` | The complete prepared body was exposed, in one result or exact pages. |
100
+ | `partial` | Recognizable fragments were exposed without establishing the full body. |
101
+ | `none` | No exposure was detected within a complete collection's matching scope. |
102
+ | `unknown` | No exposure was detected, but collection gaps prevent an absence claim. |
103
+ | `ambiguous` | Body exposure could not distinguish identically registered bodies. |
104
+
105
+ `collectionStatus` is `complete`, `partial`, or `unavailable`. No parsed hook
106
+ receipts means `unavailable`; otherwise collection gaps mean `partial`.
107
+ Recognized partial bodies and ambiguous identities alone do not indicate a
108
+ collection failure. Their existing `incompleteReasons` still keep the aggregate
109
+ activation observation partial for compatibility. Other reason codes, including
110
+ truncated call diagnostics, keep collection partial. Older observations without
111
+ these new fields have unknown exposure and collection status; do not infer them
112
+ from an empty `observedSkills` list.
113
+
114
+ Exposure statuses use all parsed receipts before the diagnostic `calls` list is
115
+ bounded. A proven `full` or `partial` exposure survives collection gaps, including
116
+ an earlier pending call, missing lifecycle hooks or an invalid receipt tail.
117
+ Full exposure takes precedence over fragments; ambiguous identities cannot
118
+ establish exposure of a particular named skill. `none` does not establish that
119
+ the skill was never used outside the prepared catalog and matching scope.
120
+ Recognizable fragments may be shared by several skill bodies. These statuses
121
+ describe content exposure to a tool caller, not instruction compliance, model
122
+ visibility or selection order.
123
+
124
+ The Codex adapter also advertises `sevro.host.skill-exposure-claims`. When at
125
+ least one skill has `full` or `partial` exposure, it emits that observation with
126
+ `completeness: complete`, `method: tool_result_body_match`,
127
+ `scope: positive_exposure`, the original `collectionStatus`, and `skillExposures`
128
+ containing only those positive entries. It is absent when no unambiguous
129
+ positive exposure is available. Extensions can require this evidence for a
130
+ specific positive assertion: accept only `full` for complete body exposure,
131
+ or explicitly accept `partial` when recognizable exposure suffices. A `partial`
132
+ entry is not a full activation. This receipt cannot establish absence, first
133
+ selection, a complete sequence or successful execution; collection remains
134
+ independently qualified. Existing activation and primary-selection requirements
135
+ are unchanged. Claude's native `Skill` seam does not emit body-exposure statuses
136
+ or these positive body-exposure claims.
137
+
93
138
  Codex observations include `incompleteReasons`: an empty array for a complete
94
- collection, or all applicable reason codes for a partial collection. Older
139
+ aggregate activation observation, or all applicable reason codes for a partial
140
+ aggregate observation. The separate `collectionStatus` isolates collection
141
+ health from partial bodies and body ambiguity. Older
95
142
  observations without this field have unknown reasons; their missing details
96
143
  cannot be reconstructed from balanced native call/return counts.
97
144
 
@@ -24,6 +24,26 @@ Preparation/candidate/grading/evidence progress appears on stderr. Stage complet
24
24
  means the attempt ended; read the three result states to determine its outcome.
25
25
  See the [evaluation workflow](evaluation-workflow.md) for the complete progression.
26
26
 
27
+ ## Interpret skill exposure
28
+
29
+ In retained Codex `sevro.host.skill-activations` observations, read
30
+ `data.skillExposures` alongside `data.collectionStatus`. A skill may have `full`
31
+ or `partial` exposure even when collection is incomplete. `none` means no
32
+ exposure was detected within a complete collection's scope; `unknown` means
33
+ collection gaps prevent that conclusion. `ambiguous` means identical bodies
34
+ could not identify a named skill. Missing fields in older evidence remain
35
+ unknown.
36
+
37
+ Full exposure establishes that a tool caller received the complete prepared
38
+ body, not that the model followed it. Partial exposure establishes only
39
+ recognizable fragments. Cases that accept partial exposure must say so in
40
+ their extension policy. The separate `sevro.host.skill-exposure-claims`
41
+ observation supports positive exposure checks without treating gaps as absence
42
+ or first selection. [Native host evidence](native-hosts.md#skill-activation-evidence)
43
+ defines the fields and limits; Claude's native skill-tool evidence has a
44
+ different scope. Task verdicts and existing activation requirements retain their
45
+ current meaning.
46
+
27
47
  ## Summarize runs
28
48
 
29
49
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bjoernrochel/sevro",
3
- "version": "0.1.0-rc.20",
3
+ "version": "0.1.0-rc.22",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "A standalone evaluation runner for agentic coding capabilities",
6
6
  "type": "module",
@@ -28,6 +28,7 @@ import {
28
28
  candidateTranscriptRoots,
29
29
  } from "../native-transcripts";
30
30
  import { prepareHookMounts } from "../runtime/hooks";
31
+ import { requireNativeHookRoots } from "../native-transcript-state";
31
32
  import {
32
33
  requireRuntimeReadRoots,
33
34
  runtimeExecutableProtection,
@@ -102,9 +103,28 @@ export async function codexHookMounts(
102
103
  await rm(root, { recursive: true });
103
104
  await cp(curated, root, { recursive: true });
104
105
  }
106
+ await grantCodexHookReads(state, hooks.directories);
105
107
  return hooks;
106
108
  }
107
109
 
110
+ /** Hooks advertise paths relative to these curated copies, not the install cache. */
111
+ async function grantCodexHookReads(
112
+ state: CodexState,
113
+ roots: string[],
114
+ ): Promise<void> {
115
+ if (roots.length === 0) return;
116
+ await requireNativeHookRoots(roots, state.codexHome);
117
+ const path = join(state.codexHome, "config.toml");
118
+ const config = await readFile(path, "utf8");
119
+ const header = `[permissions.${state.profileId}.filesystem]\n`;
120
+ const grants = roots
121
+ .map((root) => `${JSON.stringify(root)} = "read"\n`)
122
+ .join("");
123
+ await writeFile(path, config.replace(header, header + grants), {
124
+ mode: 0o600,
125
+ });
126
+ }
127
+
108
128
  async function privateCodexDirectories(stateRoot: string) {
109
129
  const codexHome = join(stateRoot, "codex-home"),
110
130
  parentHome = join(stateRoot, "home"),
@@ -45,6 +45,7 @@ import {
45
45
  prepareSkillTracking,
46
46
  skillTrackingObservation,
47
47
  skillActivationClaims,
48
+ skillExposureClaims,
48
49
  type SkillTracking,
49
50
  } from "./skill-hooks";
50
51
  import {
@@ -141,6 +142,7 @@ function codexCapabilities(
141
142
  "sevro.host.hooks",
142
143
  "sevro.host.skill-activations",
143
144
  "sevro.host.skill-activation-claims",
145
+ "sevro.host.skill-exposure-claims",
144
146
  ...(entrypoint === "app-server" ? ["sevro.host.native-goal"] : []),
145
147
  "sevro.codex.plugin-marketplace",
146
148
  "sevro.codex.explicit-invocation",
@@ -761,6 +763,7 @@ async function codexObservations(context: ExecutionContext, turns: TurnResult) {
761
763
  ...(explicit ? [explicit] : []),
762
764
  tracking,
763
765
  ...skillActivationClaims(tracking),
766
+ ...skillExposureClaims(tracking),
764
767
  ];
765
768
  }
766
769
 
@@ -449,6 +449,66 @@ export function skillActivationClaims(
449
449
  ];
450
450
  }
451
451
 
452
+ export function skillExposureClaims(
453
+ observation: Awaited<ReturnType<typeof skillTrackingObservation>>,
454
+ ) {
455
+ const skillExposures = observation.data.skillExposures.filter(
456
+ (exposure) => exposure.status === "full" || exposure.status === "partial",
457
+ );
458
+ if (skillExposures.length === 0) return [];
459
+ return [
460
+ {
461
+ id: "sevro.host.skill-exposure-claims",
462
+ completeness: "complete" as const,
463
+ data: {
464
+ method: "tool_result_body_match",
465
+ scope: "positive_exposure",
466
+ collectionStatus: observation.data.collectionStatus,
467
+ skillExposures,
468
+ },
469
+ },
470
+ ];
471
+ }
472
+
473
+ function exposureSummary(
474
+ skills: HookSkill[],
475
+ events: SkillHookReceipt[],
476
+ collectionStatus: string,
477
+ ) {
478
+ const exposure = {
479
+ full: new Set(events.flatMap((event) => event.invocations)),
480
+ partial: new Set(incompleteSkills(events)),
481
+ ambiguous: new Set(events.flatMap((event) => event.ambiguousInvocations)),
482
+ };
483
+ return skills.map(({ invocation: skill }) => ({
484
+ skill,
485
+ status: skillExposureStatus(skill, exposure, collectionStatus),
486
+ }));
487
+ }
488
+
489
+ function skillExposureStatus(
490
+ skill: string,
491
+ exposure: { full: Set<string>; partial: Set<string>; ambiguous: Set<string> },
492
+ collectionStatus: string,
493
+ ) {
494
+ if (exposure.full.has(skill)) return "full" as const;
495
+ if (exposure.ambiguous.has(skill)) return "ambiguous" as const;
496
+ if (exposure.partial.has(skill)) return "partial" as const;
497
+ return collectionStatus === "complete"
498
+ ? ("none" as const)
499
+ : ("unknown" as const);
500
+ }
501
+
502
+ function exposureCollectionStatus(reasons: string[], eventCount: number) {
503
+ if (eventCount === 0) return "unavailable" as const;
504
+ return reasons.some(
505
+ (reason) =>
506
+ !["partial_skill_bodies", "ambiguous_skill_bodies"].includes(reason),
507
+ )
508
+ ? ("partial" as const)
509
+ : ("complete" as const);
510
+ }
511
+
452
512
  export async function skillTrackingObservation(
453
513
  tracking: SkillTracking,
454
514
  sessionId: string | null,
@@ -489,6 +549,10 @@ export async function skillTrackingObservation(
489
549
  ...conditions.filter(([, applies]) => applies).map(([reason]) => reason),
490
550
  ];
491
551
  const complete = incompleteReasons.length === 0;
552
+ const collectionStatus = exposureCollectionStatus(
553
+ incompleteReasons,
554
+ events.length,
555
+ );
492
556
  return {
493
557
  id: "sevro.host.skill-activations",
494
558
  completeness: complete ? ("complete" as const) : ("partial" as const),
@@ -497,6 +561,12 @@ export async function skillTrackingObservation(
497
561
  visibility: "tool_result",
498
562
  scope: "prepared_skill_bodies",
499
563
  catalogComplete: tracking.configuration.catalogComplete,
564
+ collectionStatus,
565
+ skillExposures: exposureSummary(
566
+ tracking.configuration.skills,
567
+ events,
568
+ collectionStatus,
569
+ ),
500
570
  observedSkills: [...new Set(calls.flatMap((call) => call.invocations))],
501
571
  ...primaryEvidence(events, incompleteReasons),
502
572
  partialSkills,