Skip to content

feat(observability): export an atomic sandbox governance evidence bundle #2745

Description

@imran-siddique

Problem Statement

OpenShell already exposes the ingredients needed by external governance and compliance consumers: the effective sandbox policy, sandbox configuration and revision state, and OCSF audit events. Those ingredients are currently retrieved independently, however, so a consumer cannot establish that they describe the same sandbox state or determine whether the event interval is complete.

This is complementary to #1922 (portable, durable sandbox log collection) and #2640 (trace/span correlation). Durable logs and correlation fields are valuable inputs, but they do not bind the exact effective policy, sandbox/workload identity, event bytes, and completeness state into one authoritative export.

External adapters can hash and sign bytes they receive, but they should not invent authoritative associations or claim completeness that OpenShell itself has not established.

Proposed Design

Add an atomic export command along these lines:

openshell sandbox evidence export <sandbox> --since <timestamp> --output <directory>

The export would contain:

  • manifest.json
  • effective-policy.yaml
  • events.ocsf.jsonl

manifest.json should include:

  • evidence format version;
  • immutable sandbox ID and display name;
  • OpenShell version;
  • configuration and effective-policy revision;
  • SHA-256 digest of the exact effective-policy.yaml bytes;
  • workload/image digest when available;
  • capture start and end times in epoch milliseconds;
  • an explicit complete boolean and, when false, a machine-readable reason;
  • OCSF schema version and event count;
  • SHA-256 digests of every exported file; and
  • trace/span identifiers when available.

Required invariants:

  1. effective-policy.yaml is the policy OpenShell actually enforced, not the originally submitted input.
  2. Every exported OCSF event's metadata.uid identifies the exported sandbox.
  3. OCSF product metadata records the OpenShell version that produced the event.
  4. complete: true is emitted only when OpenShell can account for the entire requested interval. Rotation, truncation, gateway restart, or unavailable history must produce complete: false with a reason.
  5. File digests cover the exact exported bytes. Existing credential redaction guarantees remain in force.
  6. The export stays vendor-neutral. OpenShell should provide authoritative evidence, not implement TRACE-specific signing or conformance logic.

Acceptance tests should cover:

  • allowed and denied events validating against the vendored OCSF schemas;
  • successful verification of all manifest digests;
  • rotation/truncation causing an explicitly incomplete export;
  • policy changes producing a new revision and digest;
  • rejection/detection of cross-sandbox file substitution; and
  • a credential-canary scan proving exported files preserve redaction.

This primitive would support SIEM ingestion, incident response, audit archives, and third-party governance formats without coupling OpenShell to any one consumer.

Alternatives Considered

  • Implement only feat(observability): investigate portable sandbox log collection #1922: durable portable logs solve retention and transport, but not policy/workload binding or an authoritative completeness assertion.
  • Let external tools stitch existing commands together: consumers cannot prove the reads were atomic or authoritatively declare completeness.
  • Persist the current gateway log buffer: it remains an event source without binding to exact policy/configuration state.
  • Use OTLP alone: OTLP is useful for semantic telemetry export, but does not by itself define an exact-byte, revision-bound evidence bundle.

Agent Investigation

Investigation was performed against OpenShell v0.0.105 (0f8fad23c4712afc1d4a7b07a06d635b030e9521):

A released external consumer demonstrates the current integration boundary and the reason the authoritative association belongs in OpenShell:

Checklist

  • I have reviewed existing issues and architecture documentation.
  • This is a concrete design proposal rather than a feature wish list.

Activity

  1. lbelyaev commented on Aug 14, 2026

    @lbelyaev

    The digest chain gives integrity — a consumer can confirm the files match manifest.json — but the manifest itself is unsigned, so it gives no authenticity. Nothing binds the manifest to the gateway that produced it. For the file digests that's fine, since a consumer can re-derive them from the bytes. complete: true is the exception: it's the one claim in the bundle a consumer can't reconstruct from the bytes, and it's the one compliance leans on hardest. As a plaintext field it's only as trustworthy as whoever handed over the tarball.

    I'd put the vendor-neutral line one step further out than the proposal does. A gateway-authority signature over the manifest — or a key-identified digest-of-manifest — isn't TRACE-specific conformance signing; it's what makes "OpenShell established this association and this completeness" actually attributable to OpenShell rather than asserted by the exporter. Consumer-specific signing still layers on top for their own formats. Without it, the strongest guarantee in the design is the one part a consumer has to take on faith.

  2. imran-siddique commented on Aug 27, 2026

    @imran-siddique
    Author

    @lbelyaev, you are right. The file hashes prove that the files match the manifest, but they do not prove that OpenShell produced the manifest. That matters most for complete: true, because a consumer cannot work that out from the exported files.

    I would change the proposal to include a detached gateway signature:

    • manifest.json contains the export facts and file hashes.
    • manifest.sig signs the exact bytes of manifest.json.
    • The signature identifies the gateway key and signing algorithm.
    • The verifier checks that key against the OpenShell deployment authority it trusts.

    Consumer formats such as TRACE can still add their own signatures later. This first signature has a narrower job: prove that this OpenShell gateway made the policy, sandbox, event-range, and completeness claims in the manifest.

    This also adds a needed acceptance test: changing complete, a file hash, the sandbox ID, or the capture interval must make signature verification fail.

    Thank you for catching the gap.

  3. added
    state:needs-infoAssessment needs specific evidence or reproduction details
    and removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Aug 28, 2026
  4. lbelyaev commented on Aug 28, 2026

    @lbelyaev

    @lbelyaev, you are right. The file hashes prove that the files match the manifest, but they do not prove that OpenShell produced the manifest. That matters most for complete: true, because a consumer cannot work that out from the exported files.

    I would change the proposal to include a detached gateway signature:

    • manifest.json contains the export facts and file hashes.
    • manifest.sig signs the exact bytes of manifest.json.
    • The signature identifies the gateway key and signing algorithm.
    • The verifier checks that key against the OpenShell deployment authority it trusts.

    Consumer formats such as TRACE can still add their own signatures later. This first signature has a narrower job: prove that this OpenShell gateway made the policy, sandbox, event-range, and completeness claims in the manifest.

    This also adds a needed acceptance test: changing complete, a file hash, the sandbox ID, or the capture interval must make signature verification fail.

    Thank you for catching the gap.

    That's exactly the shape — detached manifest.sig over the canonical bytes, key-identified, verified against the deployment authority. The tamper test (mutating complete, a hash, the sandbox ID, or the interval must fail verification) is the right acceptance bar.

    One thing the signature leaves open: invariant 4 makes complete: true a promise OpenShell makes about the interval, and the signature proves OpenShell made it — but a consumer still can't independently check it. They're trusting the gateway's completeness logic rather than verifying it from the bundle. OCSF already defines metadata.sequence for exactly this, and OpenShell doesn't populate it today — if each exported event carried a per-sandbox sequence, a missing number would be a provable gap, and a verifier could confirm the interval has no holes itself. That turns invariant 4 from a producer-side assertion into something the consumer can check. Happy to help with that acceptance test.

  5. imran-siddique commented on Sep 9, 2026

    @imran-siddique
    Author

    @lbelyaev accepted, and this is the second time you have moved the proposal somewhere better than where I had it.

    The distinction you are drawing is the one that matters and I had blurred it. A detached manifest.sig establishes that OpenShell asserted complete: true. It does nothing about whether the assertion is correct, because the consumer is still trusting the gateway's completeness logic rather than checking it. Attribution is not verification, and I made that argument elsewhere this month while leaving it unmade here.

    Folding it in. Revised shape:

    • manifest.json carries the export facts and file hashes.
    • manifest.sig signs its exact bytes, key-identified, verified against the OpenShell deployment authority the consumer trusts.
    • Each exported event carries a per-sandbox metadata.sequence.
    • The verifier checks the sequence range itself for holes, rather than reading complete: true and stopping.

    I checked the field rather than taking it on trust: metadata.sequence is present on the OCSF metadata object at v1.9.0, typed integer_t, described as making the exact ordering of events unambiguous regardless of event-time precision. It is optional there, so populating it is a decision OpenShell makes rather than something the schema already obliges, which I think strengthens the case rather than weakening it: the field exists, the semantics are already defined, and nothing has to be invented.

    The acceptance bar then has two halves. The tamper half stands as before: mutating complete, a file hash, the sandbox ID, or the capture interval must fail signature verification. The completeness half is new and is the one worth your offer of help: removing an event from the export must be detectable from the bundle alone, without asking the gateway, because the sequence has a hole.

    One boundary I want stated in the proposal rather than discovered by a consumer later. Sequence numbers prove nothing was removed after a number was assigned. An event suppressed before assignment leaves no hole and no trace, and a clean interval must not be read as proof that everything which happened was exported. So the verifier's output should distinguish three results rather than two: verified complete over the covered range, a provable gap, and not established. The third is not a failure and it is certainly not a pass, and if the bundle format cannot express it, the first consumer to hit it will record it as a pass.

    That last part is the same shape as complete: true itself, one level down, which is what makes me confident it is worth building in now.

  6. lbelyaev commented on Sep 9, 2026

    @lbelyaev

    @lbelyaev accepted, and this is the second time you have moved the proposal somewhere better than where I had it.

    The distinction you are drawing is the one that matters and I had blurred it. A detached manifest.sig establishes that OpenShell asserted complete: true. It does nothing about whether the assertion is correct, because the consumer is still trusting the gateway's completeness logic rather than checking it. Attribution is not verification, and I made that argument elsewhere this month while leaving it unmade here.

    Folding it in. Revised shape:

    • manifest.json carries the export facts and file hashes.
    • manifest.sig signs its exact bytes, key-identified, verified against the OpenShell deployment authority the consumer trusts.
    • Each exported event carries a per-sandbox metadata.sequence.
    • The verifier checks the sequence range itself for holes, rather than reading complete: true and stopping.

    I checked the field rather than taking it on trust: metadata.sequence is present on the OCSF metadata object at v1.9.0, typed integer_t, described as making the exact ordering of events unambiguous regardless of event-time precision. It is optional there, so populating it is a decision OpenShell makes rather than something the schema already obliges, which I think strengthens the case rather than weakening it: the field exists, the semantics are already defined, and nothing has to be invented.

    The acceptance bar then has two halves. The tamper half stands as before: mutating complete, a file hash, the sandbox ID, or the capture interval must fail signature verification. The completeness half is new and is the one worth your offer of help: removing an event from the export must be detectable from the bundle alone, without asking the gateway, because the sequence has a hole.

    One boundary I want stated in the proposal rather than discovered by a consumer later. Sequence numbers prove nothing was removed after a number was assigned. An event suppressed before assignment leaves no hole and no trace, and a clean interval must not be read as proof that everything which happened was exported. So the verifier's output should distinguish three results rather than two: verified complete over the covered range, a provable gap, and not established. The third is not a failure and it is certainly not a pass, and if the bundle format cannot express it, the first consumer to hit it will record it as a pass.

    That last part is the same shape as complete: true itself, one level down, which is what makes me confident it is worth building in now.

    The three-valued result is the right call, and "not-established" being first-class is the part I'd least want dropped — a format that can only say pass/fail records the third case as a pass exactly when it matters most. Same shape as complete: true, one level down.

    One design input from your pre-assignment boundary: where the sequence is assigned sets how wide "not-established" is. OpenShell already has a single emit point — events pass through ocsf_emit! / emit_ocsf_event before the JSONL sink, and the sink assigns no ordering of its own. Stamping the per-sandbox sequence at emit rather than at serialization shrinks "not-established" to the smallest it can be: events suppressed before ocsf_emit! was ever called. Anything reaching emit gets a number, so any drop between emit and export leaves a hole the verifier catches; assigning later only pulls more of the pipeline into the untrustable window.

    On the completeness half — the case that matters is that a removed event must show as a provable gap from the bundle alone, and an empty interval must land as not-established rather than complete. Worth pinning both as explicit acceptance cases so the three-valued output is testable, not just described.

  7. imran-siddique commented on Sep 11, 2026

    @imran-siddique
    Author

    Taking the emit-point input, and it is the better placement. Stamping the per-sandbox sequence at ocsf_emit! rather than at serialization makes the untrustable window as narrow as the architecture allows: anything that reaches emit gets a number, so every drop between emit and export leaves a hole, and "not established" collapses to exactly one case, an event suppressed before ocsf_emit! was ever called. Assigning at serialization would pull the whole sink path into that window for no benefit.

    So the shape is settled from my side:

    • manifest.json carries export facts and file hashes; manifest.sig signs its exact bytes, key-identified, verified against the deployment authority the consumer trusts.
    • Per-sandbox metadata.sequence stamped at ocsf_emit!, using the existing OCSF integer_t field at v1.9.0.
    • The verifier checks the sequence range for holes rather than reading complete: true.
    • Three-valued output: verified complete over the covered range, provable gap, not established.

    Two acceptance cases to pin now, so the third value is testable rather than described:

    1. Remove one event from a valid export. The verifier must return a provable gap, from the bundle alone, without querying the gateway.
    2. Export an interval in which nothing was emitted. The verifier must return not established, not complete. A clean empty interval is the exact case a two-valued format records as a pass.

    Add a third if you want the tamper half covered in the same suite: mutate complete, a file hash, the sandbox ID or the capture interval, and signature verification must fail.

    What I would like to agree: who implements, and by when. I can write the verifier side and the acceptance cases against a bundle you produce. If that split works, name a target and I will work to it. Six weeks of good design on an issue is worth less than a rough implementation that runs.

  8. lbelyaev commented on Sep 15, 2026

    @lbelyaev

    Taking the emit-point input, and it is the better placement. Stamping the per-sandbox sequence at ocsf_emit! rather than at serialization makes the untrustable window as narrow as the architecture allows: anything that reaches emit gets a number, so every drop between emit and export leaves a hole, and "not established" collapses to exactly one case, an event suppressed before ocsf_emit! was ever called. Assigning at serialization would pull the whole sink path into that window for no benefit.

    So the shape is settled from my side:

    • manifest.json carries export facts and file hashes; manifest.sig signs its exact bytes, key-identified, verified against the deployment authority the consumer trusts.
    • Per-sandbox metadata.sequence stamped at ocsf_emit!, using the existing OCSF integer_t field at v1.9.0.
    • The verifier checks the sequence range for holes rather than reading complete: true.
    • Three-valued output: verified complete over the covered range, provable gap, not established.

    Two acceptance cases to pin now, so the third value is testable rather than described:

    1. Remove one event from a valid export. The verifier must return a provable gap, from the bundle alone, without querying the gateway.
    2. Export an interval in which nothing was emitted. The verifier must return not established, not complete. A clean empty interval is the exact case a two-valued format records as a pass.

    Add a third if you want the tamper half covered in the same suite: mutate complete, a file hash, the sandbox ID or the capture interval, and signature verification must fail.

    What I would like to agree: who implements, and by when. I can write the verifier side and the acceptance cases against a bundle you produce. If that split works, name a target and I will work to it. Six weeks of good design on an issue is worth less than a rough implementation that runs.

    Agreed on the split — you take the verifier and the acceptance cases, I take the producer side, and I'm ready to work to a target on it. The only blocker is commit access: the producer half lands as a commit here, and I'm a Contributor without write access yet — my vouch request is #2377. So I can't put my name on a merge date until that resolves.

    What I can do in the meantime: keep the producer design and the two completeness cases precise enough that your verifier has a stable contract to build against, and validate a produced bundle against your test vectors as they land. The verifier side isn't blocked either way — the shape is settled (per-sandbox metadata.sequence at ocsf_emit!, detached manifest.sig, three-valued output), so it can move now regardless of who implements the producer.

  9. imran-siddique commented on Sep 21, 2026

    @imran-siddique
    Author

    @lbelyaev the verifier and synthetic acceptance cases are ready in agentrust-io/integrations#204. The proposed manifest includes signed first/last sequence numbers so missing edge events are detectable. Could you review the producer contract before wiring up an export? No live OpenShell compatibility is claimed yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:needs-infoAssessment needs specific evidence or reproduction detailstopic:observabilityLogging, metrics, and observability work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions