From 44ee431384d326b361ec57b92d59e52aececc8d9 Mon Sep 17 00:00:00 2001 From: Antonio Antenore Date: Sun, 19 Jul 2026 12:20:41 +0200 Subject: [PATCH] fix: align StageFabric success evidence contract --- README.md | 1 + adapters/stagefabric.mapping.yaml | 2 +- docs/adr/0005-run-bound-execution-evidence.md | 2 +- docs/architecture.md | 2 +- docs/contracts-and-conformance.md | 2 +- docs/interoperability.md | 2 +- .../execution-passport/v2/README.md | 2 +- docs/threat-model.md | 1 + scripts/package-smoke.mjs | 11 +- src/adapters/stagefabric-evidence.ts | 108 +++++++++++------- test/fixtures/stagefabric/README.md | 4 +- test/stagefabric-evidence.test.ts | 79 +++++++++++++ 12 files changed, 162 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index e81eb4f..8899868 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ Read [Architecture](docs/architecture.md), [Contracts and conformance](docs/cont - An unsigned Execution Passport proves deterministic integrity and cross-artifact binding only. Authenticity requires an externally verified DSSE envelope or equivalent trusted channel, and consumers must make that requirement explicit so removing a signature cannot silently downgrade policy. - The OASF subject digest proves which opaque record was bound; it does not prove that the record is semantically valid OASF. Validate it before passport creation with the specification owner’s tooling. - Execution-placement evidence enters the predicate only as a strict, run-bound, observation-only descriptor with a normalized observation time and no payload or `content` field. A StageFabric descriptor hashes the exact canonical JSON file bytes, including its trailing LF. Descriptor metadata must come from a trusted producer and be redacted or pseudonymized when sensitive; its optional stable URI may be omitted. Full provider result objects are outside the Passport contract. +- The StageFabric adapter follows its finalized `0.7.0-alpha.1` successful-run contract: trace events are either completed placements or pre-output retries with status `429`, `502`, `503`, or `504`. Terminal failures, arbitrary HTTP codes, missing retry codes, and codes attached to completed events fail closed. - A safe fingerprint binds the declared canonical intent. It does not prove that an upstream intent normalizer chose the correct meaning. - Adapter documents are integration contracts, not bundled protocol SDKs or policy engines. - The current release is an alpha contract surface. Version consumers explicitly before production adoption. diff --git a/adapters/stagefabric.mapping.yaml b/adapters/stagefabric.mapping.yaml index f9275e4..8905fa0 100644 --- a/adapters/stagefabric.mapping.yaml +++ b/adapters/stagefabric.mapping.yaml @@ -7,7 +7,7 @@ direction: inbound contractBindings: - strataContract: ExecutionEvidenceBinding protocolObject: ExecutionPlacementEvidence - notes: The executable adapter validates strict source schema, semantic trace, timestamp grammar, seal, and exact canonical-JSON-plus-LF writer bytes; converts the prefixed run digest; normalizes observedAt; fixes authority to observation-only; and hashes the retrievable file bytes. + notes: The executable adapter validates strict successful-run source schema, admits only completed events or pre-output retries with status 429/502/503/504, verifies semantic trace, timestamp grammar, seal, and exact canonical-JSON-plus-LF writer bytes; converts the prefixed run digest; normalizes observedAt; fixes authority to observation-only; and hashes the retrievable file bytes. - strataContract: ExecutionPassport protocolObject: predicate executionEvidence entry notes: Passport creation rejects a source from another run. No payload or content field is admitted, and placement never grants executable authority. diff --git a/docs/adr/0005-run-bound-execution-evidence.md b/docs/adr/0005-run-bound-execution-evidence.md index 37f63a1..1e49f64 100644 --- a/docs/adr/0005-run-bound-execution-evidence.md +++ b/docs/adr/0005-run-bound-execution-evidence.md @@ -12,7 +12,7 @@ Execution Passport v2 requires every `ExecutionEvidenceBinding` to carry `runIdD Provider integrations are explicit reduction adapters. They validate the external artifact schema and producer seal, bind the complete artifact by digest, and emit only a strict descriptor. -The StageFabric adapter binds the exact canonical JSON file bytes written by StageFabric, including its trailing LF, rather than hashing a reparsed object. Its CLI path rejects alternative encodings so a URI can be verified byte-for-byte against the descriptor. +The StageFabric adapter binds the exact canonical JSON file bytes written by StageFabric, including its trailing LF, rather than hashing a reparsed object. Its CLI path rejects alternative encodings so a URI can be verified byte-for-byte against the descriptor. The finalized `0.7.0-alpha.1` successful-run contract admits only completed events and pre-output retries with status `429`, `502`, `503`, or `504`; terminal failures require a distinct producer artifact and cannot be relabeled into this binding. The evidence can explain where execution was observed. It cannot issue a grant, widen a grant, authorize a capability, or override an authority check. Provider payloads and result objects remain outside the Passport. diff --git a/docs/architecture.md b/docs/architecture.md index fa97203..a6016ea 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -78,7 +78,7 @@ Passport creation is a deterministic packaging step after conformance evaluation 2. the canonical digest of the complete `ConformanceReport`, including its own `digest` field; 3. the canonical digest of one opaque, externally validated OASF record. -Optional execution-placement evidence is reduced to a `ResourceDescriptor` with no payload or `content` field. AgenticStrata admits only the descriptor, producer identity, fixed role, canonical run-identifier digest, normalized observation time, `observation-only` authority, and `content-free` disclosure marker. A StageFabric adapter validates the source artifact, semantic trace, timestamp grammar, producer seal, and exact canonical-JSON-plus-LF writer bytes before reduction. Descriptor metadata must be trusted and redacted or pseudonymized when sensitive, and its URI may be omitted. A placement observation never grants authority; a descriptor from another run or after the report is rejected. +Optional execution-placement evidence is reduced to a `ResourceDescriptor` with no payload or `content` field. AgenticStrata admits only the descriptor, producer identity, fixed role, canonical run-identifier digest, normalized observation time, `observation-only` authority, and `content-free` disclosure marker. A StageFabric adapter validates the source artifact, its successful-run-only trace (`completed` or a pre-output retry with `429`, `502`, `503`, or `504`), timestamp grammar, producer seal, and exact canonical-JSON-plus-LF writer bytes before reduction. Descriptor metadata must be trusted and redacted or pseudonymized when sensitive, and its URI may be omitted. A placement observation never grants authority; a descriptor from another run or after the report is rejected. The core emits an unsigned Statement. A replaceable external adapter may wrap its exact RFC 8785 bytes in DSSE and use Sigstore or another trust system. Signature verification, signer identity, transparency-log policy, and trusted time remain consumer-owned controls. diff --git a/docs/contracts-and-conformance.md b/docs/contracts-and-conformance.md index f2196f1..0e54554 100644 --- a/docs/contracts-and-conformance.md +++ b/docs/contracts-and-conformance.md @@ -50,7 +50,7 @@ The predicate records `runStatus` and `conformanceStatus` separately. Creation d OASF input is accepted only as an opaque I-JSON object and canonicalized for its subject digest. AgenticStrata deliberately does not carry an OASF schema and therefore does not claim semantic validity. A caller must validate the record externally and provide the owner-declared media type. -`executionEvidence` accepts only strict bindings with no payload or `content` field. Every binding carries the canonical digest of the observed run identifier, normalized observation time, and fixed `observation-only` authority; a run mismatch or observation later than the report fails Passport creation. The StageFabric reduction adapter verifies the source schema, semantic invariants, producer seal, timestamp grammar, and exact writer encoding, then binds the complete canonical-JSON-plus-LF file bytes. AgenticStrata never imports a full provider result. Descriptor metadata must be trusted and redacted or pseudonymized when sensitive, and the URI may be omitted. +`executionEvidence` accepts only strict bindings with no payload or `content` field. Every binding carries the canonical digest of the observed run identifier, normalized observation time, and fixed `observation-only` authority; a run mismatch or observation later than the report fails Passport creation. The StageFabric reduction adapter verifies the source schema, semantic invariants, producer seal, timestamp grammar, and exact writer encoding, then binds the complete canonical-JSON-plus-LF file bytes. Its successful-run trace is closed to `completed` events and pre-output retries with status `429`, `502`, `503`, or `504`; terminal failures and every other status shape are invalid. AgenticStrata never imports a full provider result. Descriptor metadata must be trusted and redacted or pseudonymized when sensitive, and the URI may be omitted. The Passport is created after assessment and is not required by a conformance rule. Making it a prerequisite of the report it contains would create a circular dependency. diff --git a/docs/interoperability.md b/docs/interoperability.md index 26c367d..5168807 100644 --- a/docs/interoperability.md +++ b/docs/interoperability.md @@ -12,7 +12,7 @@ AgenticStrata owns application-level runtime invariants. It deliberately leaves | Policy engine | Grant, approval, attenuation query | The external enforcement point remains authoritative. | | OASF | Opaque agent-record subject descriptor | AgenticStrata binds canonical bytes but delegates semantic validation and media-type ownership. | | in-toto / DSSE | Execution Passport Statement and optional external envelope | The core owns deterministic binding; signing, identity, transparency, and trusted time stay outside it. | -| StageFabric | Sealed content-free placement artifact reduced to an evidence descriptor | Adapter verifies schema, seal, exact writer bytes, run binding, and observation time; the result is observation-only and cannot expand authority. | +| StageFabric | Sealed content-free placement artifact reduced to an evidence descriptor | Adapter verifies the finalized successful-run trace (`completed` or retry `429`/`502`/`503`/`504`), seal, exact writer bytes, run binding, and observation time; the result is observation-only and cannot expand authority. | | Other execution-placement provider | Content-free, run-bound evidence descriptor | No payload or `content` field is admitted; producer validation remains an explicit adapter responsibility. | Adapter YAML files are declarative mappings with an explicit `lossPolicy`. They contain no vendor SDK and can be replaced without changing the core contract registry. diff --git a/docs/spec/attestations/execution-passport/v2/README.md b/docs/spec/attestations/execution-passport/v2/README.md index 5db77b0..6e83a97 100644 --- a/docs/spec/attestations/execution-passport/v2/README.md +++ b/docs/spec/attestations/execution-passport/v2/README.md @@ -45,7 +45,7 @@ Every `ExecutionEvidenceBinding` contains exactly: The builder rejects a binding when `runIdDigest` does not match the bound run, when two evidence artifacts share a digest, when `observedAt` is later than the report, or when the descriptor is not strict. An observation can support lineage but cannot grant, widen, or substitute executable authority. -For StageFabric, the resource digest is SHA-256 over the exact UTF-8 bytes written by its safe writer: canonical JSON for the complete evidence object, including the producer seal, followed by one LF byte. The CLI rejects YAML, pretty JSON, missing or extra whitespace, and other reserializations. If a descriptor carries a URI, retrieval must return those exact bytes. +For StageFabric, the resource digest is SHA-256 over the exact UTF-8 bytes written by its safe writer: canonical JSON for the complete evidence object, including the producer seal, followed by one LF byte. The adapter accepts only its successful-run trace variants: `completed`, or `retryable_pre_output_status` with `429`, `502`, `503`, or `504`. The CLI rejects YAML, pretty JSON, missing or extra whitespace, and other reserializations. If a descriptor carries a URI, retrieval must return those exact bytes. Provider adapters must validate their source contract and seal before creating a binding. They must not copy stage names, targets, prompts, model input or output, credentials, paths, or provider result objects into the binding. Descriptor metadata can still be sensitive and must be redacted or pseudonymized; the URI is optional. diff --git a/docs/threat-model.md b/docs/threat-model.md index bc2fa51..7daa431 100644 --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -36,6 +36,7 @@ Model output, retrieved content, external messages, tool descriptions, and impor | A Passport is interpreted as proof of success | The predicate carries separate explicit run and conformance statuses and permits failed or incomplete states. | Consumers must still enforce the statuses appropriate to their decision. | | Raw execution data leaks through placement evidence | The schema and CLI reject payload/content fields, inline or local URI schemes, credentialed HTTPS references, query strings, fragments, and oversized URIs. | Descriptor names, producer identifiers, media types, HTTPS paths, and URNs remain metadata channels; accept them only from trusted producers and redact, pseudonymize, or omit optional URIs. | | Valid placement evidence is replayed onto another run or treated as permission | V2 binds each descriptor to `SHA256(RFC8785(runId))`, fixes its authority to `observation-only`, and rejects mismatches before Passport creation. | A compromised producer can still create false but internally consistent observations; authenticate and qualify producers externally. | +| A successful StageFabric observation smuggles a terminal failure or arbitrary HTTP status | A closed AJV union admits only `completed` events or pre-output retries with status `429`, `502`, `503`, or `504`; static types encode the same discriminated contract. | A future producer-contract change requires an explicit, versioned adapter update. | | An external observation is backdated, postdates its report, or is reserialized behind its URI | The binding carries normalized `observedAt`; report chronology includes it; the StageFabric adapter verifies the producer timestamp grammar and hashes exact canonical writer bytes including LF. | Dates are self-asserted unless the producer is authenticated and backed by trusted time. Availability of the URI remains external. | | A report claims success after checks are removed, reordered, duplicated, or changed | Creation requires unique ordered check identifiers, binds that order to `rulesDigest`, and derives report status from the checks. | Hash consistency does not authenticate the evaluator or its implementation. | | A report or Passport appears to predate evidence it assesses | Creation requires report generation after every included observed evidence timestamp and issuance after report generation. | These are self-asserted consistency checks, not trusted timestamps; non-repudiation requires external infrastructure. | diff --git a/scripts/package-smoke.mjs b/scripts/package-smoke.mjs index 565a75b..04234b1 100644 --- a/scripts/package-smoke.mjs +++ b/scripts/package-smoke.mjs @@ -41,7 +41,7 @@ try { 'const report = runConformance(result.bundle, "enterprise");', 'const sha256 = (value) => `sha256:${digestValue(value)}`;', 'const identity = { stageIdDigest: sha256("stage"), targetIdDigest: sha256("target"), zoneDigest: sha256("zone"), adapterKindDigest: sha256("adapter") };', - 'const unsigned = { apiVersion: "stagefabric.dev/v1alpha1", kind: "ExecutionPlacementEvidence", producer: "stagefabric", disclosure: "content-free", authority: "observation-only", runIdDigest: sha256(result.bundle.execution.runId), observedAt: "2026-07-17T12:00:00.000Z", planDigest: sha256("plan"), bindingDigest: sha256("binding"), snapshotDigest: sha256("snapshot"), egressDigest: sha256("egress"), placements: [{ ...identity, attempt: 1, status: "succeeded", reasonCode: "completed" }], trace: [{ ...identity, attempt: 1, status: "succeeded", reasonCode: "completed" }] };', + 'const unsigned = { apiVersion: "stagefabric.dev/v1alpha1", kind: "ExecutionPlacementEvidence", producer: "stagefabric", disclosure: "content-free", authority: "observation-only", runIdDigest: sha256(result.bundle.execution.runId), observedAt: "2026-07-17T12:00:00.000Z", planDigest: sha256("plan"), bindingDigest: sha256("binding"), snapshotDigest: sha256("snapshot"), egressDigest: sha256("egress"), placements: [{ ...identity, attempt: 2, status: "succeeded", reasonCode: "completed" }], trace: [{ ...identity, attempt: 1, status: "failed", reasonCode: "retryable_pre_output_status", statusCode: 503 }, { ...identity, attempt: 2, status: "succeeded", reasonCode: "completed" }] };', 'const executionEvidence = createStageFabricExecutionEvidenceBinding({ ...unsigned, digest: sha256(unsigned) });', 'const passport = createExecutionPassport({ bundle: result.bundle, report, oasfRecord: { opaqueExternalRecord: true }, oasfMediaType: "application/json", executionEvidence: [executionEvidence] });', 'if (report.status !== "pass" || report.runStatus !== "completed" || result.readBack !== true || !validateDocument(passport).valid || passport.subject.length !== 3 || passport.predicate.executionEvidence.length !== 1) process.exit(2);', @@ -93,12 +93,19 @@ try { snapshotDigest: sha256("snapshot"), egressDigest: sha256("egress"), placements: [ - { ...identity, attempt: 1, status: "succeeded", reasonCode: "completed" } + { ...identity, attempt: 2, status: "succeeded", reasonCode: "completed" } ], trace: [ { ...identity, attempt: 1, + status: "failed", + reasonCode: "retryable_pre_output_status", + statusCode: 503 + }, + { + ...identity, + attempt: 2, status: "succeeded", reasonCode: "completed" } diff --git a/src/adapters/stagefabric-evidence.ts b/src/adapters/stagefabric-evidence.ts index dd8c2e3..7b006c7 100644 --- a/src/adapters/stagefabric-evidence.ts +++ b/src/adapters/stagefabric-evidence.ts @@ -13,33 +13,43 @@ export const STAGEFABRIC_EXECUTION_EVIDENCE_MEDIA_TYPE = type StageFabricDigest = `sha256:${string}`; -export interface StageFabricExecutionPlacement { +export const STAGEFABRIC_RETRYABLE_PRE_OUTPUT_STATUS_CODES = [ + 429, 502, 503, 504 +] as const; + +export type StageFabricRetryablePreOutputStatusCode = + (typeof STAGEFABRIC_RETRYABLE_PRE_OUTPUT_STATUS_CODES)[number]; + +export interface StageFabricExecutionTraceIdentity { stageIdDigest: StageFabricDigest; targetIdDigest: StageFabricDigest; zoneDigest: StageFabricDigest; adapterKindDigest: StageFabricDigest; +} + +export interface StageFabricExecutionPlacement + extends StageFabricExecutionTraceIdentity { attempt: number; status: "succeeded"; reasonCode: "completed"; } -export interface StageFabricExecutionTraceEvent { - stageIdDigest: StageFabricDigest; - targetIdDigest: StageFabricDigest; - zoneDigest: StageFabricDigest; - adapterKindDigest: StageFabricDigest; - attempt: number; - status: "succeeded" | "failed"; - reasonCode: - | "completed" - | "retryable_pre_output_status" - | "adapter_not_registered" - | "adapter_failed" - | "invalid_outputs" - | "input_policy_rejected" - | "output_policy_rejected"; - statusCode?: number; -} +export type StageFabricExecutionTraceEvent = + StageFabricExecutionTraceIdentity & + ( + | { + attempt: number; + status: "succeeded"; + reasonCode: "completed"; + statusCode?: never; + } + | { + attempt: number; + status: "failed"; + reasonCode: "retryable_pre_output_status"; + statusCode: StageFabricRetryablePreOutputStatusCode; + } + ); export interface StageFabricExecutionPlacementEvidence { apiVersion: "stagefabric.dev/v1alpha1"; @@ -81,6 +91,19 @@ const identityDigestProperties = { zoneDigest: digestSchema, adapterKindDigest: digestSchema } as const; +const traceEventProperties = { + ...identityDigestProperties, + attempt: { type: "integer", minimum: 1, maximum: 33 } +} as const; +const traceEventRequired = [ + "stageIdDigest", + "targetIdDigest", + "zoneDigest", + "adapterKindDigest", + "attempt", + "status", + "reasonCode" +] as const; const schema = { type: "object", @@ -126,33 +149,30 @@ const schema = { minItems: 1, maxItems: 33792, items: { - type: "object", - additionalProperties: false, - properties: { - ...identityDigestProperties, - attempt: { type: "integer", minimum: 1, maximum: 33 }, - status: { enum: ["succeeded", "failed"] }, - reasonCode: { - enum: [ - "completed", - "retryable_pre_output_status", - "adapter_not_registered", - "adapter_failed", - "invalid_outputs", - "input_policy_rejected", - "output_policy_rejected" - ] + oneOf: [ + { + type: "object", + additionalProperties: false, + properties: { + ...traceEventProperties, + status: { const: "succeeded" }, + reasonCode: { const: "completed" } + }, + required: traceEventRequired }, - statusCode: { type: "integer", minimum: 100, maximum: 599 } - }, - required: [ - "stageIdDigest", - "targetIdDigest", - "zoneDigest", - "adapterKindDigest", - "attempt", - "status", - "reasonCode" + { + type: "object", + additionalProperties: false, + properties: { + ...traceEventProperties, + status: { const: "failed" }, + reasonCode: { const: "retryable_pre_output_status" }, + statusCode: { + enum: STAGEFABRIC_RETRYABLE_PRE_OUTPUT_STATUS_CODES + } + }, + required: [...traceEventRequired, "statusCode"] + } ] } }, diff --git a/test/fixtures/stagefabric/README.md b/test/fixtures/stagefabric/README.md index 10d76c0..520320b 100644 --- a/test/fixtures/stagefabric/README.md +++ b/test/fixtures/stagefabric/README.md @@ -1,8 +1,8 @@ # StageFabric interoperability golden -`execution-placement-evidence.json` was emitted with StageFabric `0.7.0-alpha.1` at commit `d6bd6b5` by its exported `sealExecutionPlacementEvidence` and `canonicalJson` functions. It covers one retry followed by fallback success, a second successful stage, and the minute-precision timestamp accepted by the StageFabric contract. +`execution-placement-evidence.json` was emitted with StageFabric `0.7.0-alpha.1` by its exported `sealExecutionPlacementEvidence` and `canonicalJson` functions; that producer tree is publicly reachable on `main` at commit `9c6bac4`. The successful-run contract finalized at commit `743752e`; this fixture remains byte-for-byte compatible because its only failed event is an allowed pre-output retry with status `503`. It covers that retry followed by fallback success, a second successful stage, and the minute-precision timestamp accepted by the StageFabric contract. - Producer seal over evidence content without `digest`: `sha256:8bedc2e796afff222dbb15c35eb2f07f4dde50d97e49d669d813e06950fb0c79` - SHA-256 over exact emitted file bytes, including trailing LF: `aa0d03a6af7b6e5e03afd1d43530077578d8e67cba58295f7d86c88bd2fad632` -The AgenticStrata tests must consume these bytes without rebuilding the fixture through AgenticStrata helpers. Regenerate it from StageFabric when its evidence contract changes. +The AgenticStrata tests must consume these bytes without rebuilding the fixture through AgenticStrata helpers. They also reject terminal failure reasons, retry statuses outside `429`, `502`, `503`, and `504`, a retry without `statusCode`, and a completed event with `statusCode`. Regenerate the fixture from StageFabric when its evidence contract changes. diff --git a/test/stagefabric-evidence.test.ts b/test/stagefabric-evidence.test.ts index 28808ed..a1658fb 100644 --- a/test/stagefabric-evidence.test.ts +++ b/test/stagefabric-evidence.test.ts @@ -83,6 +83,21 @@ function passportInput(evidence: StageFabricExecutionPlacementEvidence) { } as const; } +function resealTrace( + trace: readonly unknown[], + placementAttempt: number +): unknown { + const valid = stageFabricEvidence(); + const placement = valid.placements[0]; + if (placement === undefined) throw new Error("fixture placement missing"); + const unsigned = { + ...omitDigest(valid), + placements: [{ ...placement, attempt: placementAttempt }], + trace + }; + return { ...unsigned, digest: sha256(unsigned) }; +} + describe("StageFabric execution placement evidence adapter", () => { it("matches a retrying golden artifact emitted by StageFabric", () => { const fixturePath = fileURLToPath( @@ -224,6 +239,70 @@ describe("StageFabric execution placement evidence adapter", () => { ).toThrowError(new StageFabricEvidenceError("stagefabric_evidence_invalid")); }); + it("rejects failure semantics outside the successful StageFabric contract", () => { + const completed = stageFabricEvidence().trace[0]; + if (completed === undefined) throw new Error("fixture trace event missing"); + const completedAtAttemptTwo = { ...completed, attempt: 2 }; + + expect(() => + createStageFabricExecutionEvidenceBinding( + resealTrace( + [ + { + ...completed, + attempt: 1, + status: "failed", + reasonCode: "adapter_failed" + }, + completedAtAttemptTwo + ], + 2 + ) + ) + ).toThrowError(new StageFabricEvidenceError("stagefabric_evidence_invalid")); + + expect(() => + createStageFabricExecutionEvidenceBinding( + resealTrace( + [ + { + ...completed, + attempt: 1, + status: "failed", + reasonCode: "retryable_pre_output_status", + statusCode: 418 + }, + completedAtAttemptTwo + ], + 2 + ) + ) + ).toThrowError(new StageFabricEvidenceError("stagefabric_evidence_invalid")); + + expect(() => + createStageFabricExecutionEvidenceBinding( + resealTrace( + [ + { + ...completed, + attempt: 1, + status: "failed", + reasonCode: "retryable_pre_output_status" + }, + completedAtAttemptTwo + ], + 2 + ) + ) + ).toThrowError(new StageFabricEvidenceError("stagefabric_evidence_invalid")); + + expect(() => + createStageFabricExecutionEvidenceBinding( + resealTrace([{ ...completed, statusCode: 503 }], 1) + ) + ).toThrowError(new StageFabricEvidenceError("stagefabric_evidence_invalid")); + }); + it("matches the StageFabric timestamp grammar and report chronology", () => { const resealAt = (observedAt: string) => { const unsigned = { ...omitDigest(stageFabricEvidence()), observedAt };