Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ defaults are applied; output aliases such as `EvidenceRef` describe parsed data.
traces, or other evidence.
- `hasna.work_run.v1`: normalized run receipt for agent, command, workflow, loop,
eval, test, deploy, or review work.
- `hasna.agent_run_provenance.v1`: durable control-plane provenance for agent
runs, linking task, prompt/model version, auth profile reference, sandbox,
approval policy, tool calls, costs, verifier result, commit/PR, artifacts,
provider health, and credential references without raw secret material.
- `hasna.decision_envelope.v1`: decision record with selected/skipped
resources, rationale, actor, costs, obligations, redactions, and evidence.
- `hasna.cost_estimate.v1`: money and token estimates with provider, model,
Expand Down
45 changes: 45 additions & 0 deletions examples/agent-run-provenance.invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"schema": "hasna.agent_run_provenance.v1",
"id": "run_missing_verifier",
"createdAt": "2026-07-06T13:40:00.000Z",
"taskId": "task_missing_verifier",
"repoPath": "/home/hasna/workspace/hasna/opensource/open-coders",
"agentId": "worker-x",
"runId": "run_missing_verifier",
"promptRef": {
"kind": "document",
"id": "prompt_missing_verifier",
"uri": "task://task_missing_verifier"
},
"modelRef": {
"kind": "model",
"id": "model_missing_verifier",
"sourcePackage": "@hasna/models",
"externalId": "model_missing_verifier"
},
"sandboxMode": "worktree_write",
"approvalPolicy": {
"mode": "manual",
"requiresVerifier": true,
"mutationAllowed": true
},
"startedAt": "2026-07-06T13:40:00.000Z",
"completedAt": "2026-07-06T13:41:00.000Z",
"status": "succeeded",
"commitOrPr": {
"commit": {
"kind": "commit",
"id": "commit_missing_verifier",
"externalId": "abcdef0",
"sourcePackage": "git"
}
},
"artifactRefs": [
{
"id": "ev_missing_verifier_tests",
"kind": "test_result",
"uri": "artifact://runs/run_missing_verifier/tests.txt"
}
],
"redactionStatus": "partial"
}
130 changes: 130 additions & 0 deletions examples/agent-run-provenance.valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"schema": "hasna.agent_run_provenance.v1",
"id": "run_worker_2_gateway_20260706",
"createdAt": "2026-07-06T13:40:00.000Z",
"taskId": "84481fea-7d49-4b2e-88d2-f4a8a81ecafc",
"projectId": "wks_dclch11fniur",
"repoPath": "/home/hasna/workspace/hasna/opensource/open-gateway",
"agentId": "worker-2",
"runId": "run_worker_2_gateway_20260706",
"parentRunId": "run_missing_core_worker_2",
"promptRef": {
"kind": "document",
"id": "prompt_missing_core_worker_2",
"uri": "task://84481fea-7d49-4b2e-88d2-f4a8a81ecafc"
},
"promptVersion": "2026-07-06.worker-2",
"modelRef": {
"kind": "model",
"id": "codewith-default",
"sourcePackage": "@hasna/codewith",
"externalId": "selected-session-model"
},
"modelCapabilityRef": {
"kind": "model",
"id": "model_capability_code",
"uri": "artifact://model-capabilities/codewith-default.json"
},
"authProfileRef": {
"kind": "integration",
"id": "auth_profile_live_codewith",
"sourcePackage": "@hasna/accounts",
"externalId": "live-codewith"
},
"sandboxMode": "worktree_write",
"approvalPolicy": {
"mode": "on_request",
"requiresVerifier": true,
"mutationAllowed": true,
"approvalRefs": [
{
"kind": "task",
"id": "task_approval_84481fea",
"uri": "task://84481fea-7d49-4b2e-88d2-f4a8a81ecafc"
}
]
},
"toolCalls": [
{
"id": "tool_git_diff",
"toolName": "exec_command",
"action": "git diff --check",
"status": "succeeded",
"startedAt": "2026-07-06T13:43:00.000Z",
"completedAt": "2026-07-06T13:43:01.000Z",
"evidenceRefs": [
{
"id": "ev_git_diff_check",
"kind": "command_output",
"uri": "artifact://runs/run_worker_2_gateway_20260706/git-diff-check.txt"
}
],
"redaction": "none"
}
],
"cost": {
"schema": "hasna.cost_estimate.v1",
"id": "cost_worker_2_gateway",
"createdAt": "2026-07-06T13:44:00.000Z",
"currency": "USD",
"amountMicros": 0,
"basis": "actual"
},
"startedAt": "2026-07-06T13:40:00.000Z",
"completedAt": "2026-07-06T13:45:00.000Z",
"status": "succeeded",
"verifierResult": {
"status": "passed",
"verifier": {
"kind": "agent",
"id": "adversarial-self-review",
"name": "Worker 2 adversarial self-review"
},
"summary": "Validated bounded scope, no raw secrets, and evidence-backed claims.",
"evidenceRefs": [
{
"id": "ev_adversarial_review",
"kind": "report",
"uri": "artifact://runs/run_worker_2_gateway_20260706/adversarial-review.md"
}
]
},
"commitOrPr": {
"commit": {
"kind": "commit",
"id": "commit_1065ea5",
"externalId": "1065ea5",
"sourcePackage": "git"
},
"branch": {
"kind": "branch",
"id": "missing-core-infra-control-84481fea",
"externalId": "missing-core/infra-control-84481fea",
"sourcePackage": "git"
}
},
"artifactRefs": [
{
"id": "ev_gateway_tests",
"kind": "test_result",
"uri": "artifact://runs/run_worker_2_gateway_20260706/tests.txt",
"summary": "bun run typecheck && bun test && bun run build"
}
],
"providerHealthRefs": [
{
"kind": "verification",
"id": "gateway_mock_provider_health",
"uri": "artifact://runs/run_worker_2_gateway_20260706/mock-provider-health.json"
}
],
"credentialRefs": [
{
"kind": "integration",
"id": "gateway_api_key_env",
"sourcePackage": "@hasna/gateway",
"externalId": "GATEWAY_API_KEY"
}
],
"redactionStatus": "full"
}
133 changes: 133 additions & 0 deletions src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const SCHEMA_IDS = {
resourceRef: "hasna.resource_ref.v1",
evidenceRef: "hasna.evidence_ref.v1",
workRun: "hasna.work_run.v1",
agentRunProvenance: "hasna.agent_run_provenance.v1",
decisionEnvelope: "hasna.decision_envelope.v1",
costEstimate: "hasna.cost_estimate.v1",
capabilityCard: "hasna.capability_card.v1",
Expand Down Expand Up @@ -1683,6 +1684,134 @@ export const WorkRunSchema = contractBaseSchema(SCHEMA_IDS.workRun)
});
export type WorkRun = z.infer<typeof WorkRunSchema>;

export const AgentRunSandboxModeSchema = z.enum(["none", "read_only", "workspace_write", "worktree_write", "networked", "danger_full_access"]);
export type AgentRunSandboxMode = z.infer<typeof AgentRunSandboxModeSchema>;

export const AgentRunApprovalPolicySchema = z
.object({
mode: z.enum(["never", "on_request", "on_failure", "manual", "unknown"]),
requiresVerifier: z.boolean().default(false),
mutationAllowed: z.boolean().default(false),
approvalRefs: z.array(ResourcePointerSchema).default([])
})
.strict();
export type AgentRunApprovalPolicy = z.infer<typeof AgentRunApprovalPolicySchema>;

export const AgentRunToolCallSchema = z
.object({
id: z.string().min(1),
toolName: z.string().min(1),
action: z.string().min(1).optional(),
status: ContractStatusSchema.default("unknown"),
startedAt: OptionalTimestampSchema,
completedAt: OptionalTimestampSchema,
inputHash: HashStringSchema.optional(),
outputHash: HashStringSchema.optional(),
resourceRefs: z.array(ResourcePointerSchema).default([]),
evidenceRefs: z.array(EvidencePointerSchema).default([]),
redaction: RedactionStateSchema.default("unknown")
})
.strict()
.superRefine((value, ctx) => {
if (value.startedAt && value.completedAt && Date.parse(value.completedAt) < Date.parse(value.startedAt)) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "completedAt must be after or equal to startedAt", path: ["completedAt"] });
}
});
export type AgentRunToolCall = z.infer<typeof AgentRunToolCallSchema>;

export const AgentRunVerifierResultSchema = z
.object({
status: z.enum(["passed", "failed", "not_required", "not_run", "unknown"]),
verifier: ActorPointerSchema.optional(),
summary: z.string().min(1).optional(),
proofBundleRef: ResourcePointerSchema.optional(),
evidenceRefs: z.array(EvidencePointerSchema).default([])
})
.strict()
.superRefine((value, ctx) => {
if ((value.status === "passed" || value.status === "failed") && !value.verifier && !value.proofBundleRef) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Verifier pass/fail results require verifier or proofBundleRef",
path: ["verifier"]
});
}
});
export type AgentRunVerifierResult = z.infer<typeof AgentRunVerifierResultSchema>;

export const CommitOrPrRefSchema = z
.object({
commit: ResourcePointerSchema.optional(),
branch: ResourcePointerSchema.optional(),
pullRequest: ResourcePointerSchema.optional()
})
.strict()
.superRefine((value, ctx) => {
if (!value.commit && !value.branch && !value.pullRequest) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "commitOrPr requires at least one commit, branch, or pullRequest ref",
path: ["commit"]
});
}
});
export type CommitOrPrRef = z.infer<typeof CommitOrPrRefSchema>;

export const AgentRunProvenanceSchema = contractBaseSchema(SCHEMA_IDS.agentRunProvenance)
.extend({
taskId: z.string().min(1),
projectId: z.string().min(1).optional(),
repoPath: z.string().min(1),
agentId: z.string().min(1),
runId: z.string().min(1),
parentRunId: z.string().min(1).optional(),
promptRef: ResourcePointerSchema,
promptVersion: z.string().min(1).optional(),
modelRef: ResourcePointerSchema,
modelCapabilityRef: ResourcePointerSchema.optional(),
authProfileRef: ResourcePointerSchema.optional(),
sandboxMode: AgentRunSandboxModeSchema,
approvalPolicy: AgentRunApprovalPolicySchema,
toolCalls: z.array(AgentRunToolCallSchema).default([]),
cost: CostEstimateSchema.optional(),
startedAt: OptionalTimestampSchema,
completedAt: OptionalTimestampSchema,
status: ContractStatusSchema,
verifierResult: AgentRunVerifierResultSchema.optional(),
commitOrPr: CommitOrPrRefSchema.optional(),
artifactRefs: z.array(EvidencePointerSchema).default([]),
providerHealthRefs: z.array(ResourcePointerSchema).default([]),
credentialRefs: z.array(ResourcePointerSchema).default([]),
redactionStatus: RedactionStateSchema.default("unknown")
})
.strict()
.superRefine((value, ctx) => {
if (value.runId !== value.id) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "runId must match id", path: ["runId"] });
}
if (value.startedAt && value.completedAt && Date.parse(value.completedAt) < Date.parse(value.startedAt)) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "completedAt must be after or equal to startedAt", path: ["completedAt"] });
}
if (TerminalStatuses.has(value.status) && !value.completedAt) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Terminal provenance records require completedAt", path: ["completedAt"] });
}
if (value.approvalPolicy.requiresVerifier && value.verifierResult?.status !== "passed") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Review-required runs require a passed verifierResult",
path: ["verifierResult"]
});
}
if (value.status === "succeeded" && value.artifactRefs.length === 0 && !value.commitOrPr && !value.verifierResult?.proofBundleRef) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Succeeded provenance records require artifactRefs, commitOrPr, or verifier proof",
path: ["artifactRefs"]
});
}
});
export type AgentRunProvenance = z.infer<typeof AgentRunProvenanceSchema>;

export const TrajectoryEventSchema = z
.object({
id: z.string().min(1),
Expand Down Expand Up @@ -1927,6 +2056,7 @@ export const ContractSchemaRegistry = {
[SCHEMA_IDS.resourceRef]: ResourceRefSchema,
[SCHEMA_IDS.evidenceRef]: EvidenceRefSchema,
[SCHEMA_IDS.workRun]: WorkRunSchema,
[SCHEMA_IDS.agentRunProvenance]: AgentRunProvenanceSchema,
[SCHEMA_IDS.decisionEnvelope]: DecisionEnvelopeSchema,
[SCHEMA_IDS.costEstimate]: CostEstimateSchema,
[SCHEMA_IDS.capabilityCard]: CapabilityCardSchema,
Expand Down Expand Up @@ -1954,6 +2084,7 @@ export type ContractBySchemaId = {
[SCHEMA_IDS.resourceRef]: ResourceRef;
[SCHEMA_IDS.evidenceRef]: EvidenceRef;
[SCHEMA_IDS.workRun]: WorkRun;
[SCHEMA_IDS.agentRunProvenance]: AgentRunProvenance;
[SCHEMA_IDS.decisionEnvelope]: DecisionEnvelope;
[SCHEMA_IDS.costEstimate]: CostEstimate;
[SCHEMA_IDS.capabilityCard]: CapabilityCard;
Expand All @@ -1978,6 +2109,7 @@ export type ActorRefInput = z.input<typeof ActorRefSchema>;
export type ResourceRefInput = z.input<typeof ResourceRefSchema>;
export type EvidenceRefInput = z.input<typeof EvidenceRefSchema>;
export type WorkRunInput = z.input<typeof WorkRunSchema>;
export type AgentRunProvenanceInput = z.input<typeof AgentRunProvenanceSchema>;
export type DecisionEnvelopeInput = z.input<typeof DecisionEnvelopeSchema>;
export type CostEstimateInput = z.input<typeof CostEstimateSchema>;
export type CapabilityCardInput = z.input<typeof CapabilityCardSchema>;
Expand Down Expand Up @@ -2005,6 +2137,7 @@ export type ContractInputBySchemaId = {
[SCHEMA_IDS.resourceRef]: ResourceRefInput;
[SCHEMA_IDS.evidenceRef]: EvidenceRefInput;
[SCHEMA_IDS.workRun]: WorkRunInput;
[SCHEMA_IDS.agentRunProvenance]: AgentRunProvenanceInput;
[SCHEMA_IDS.decisionEnvelope]: DecisionEnvelopeInput;
[SCHEMA_IDS.costEstimate]: CostEstimateInput;
[SCHEMA_IDS.capabilityCard]: CapabilityCardInput;
Expand Down
1 change: 1 addition & 0 deletions tests/examples.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ContractSchemaRegistry, SCHEMA_IDS, type KnownSchemaId, validateContrac

const examplesDir = join(import.meta.dir, "..", "examples");
const expectedInvalidIssuePaths: Record<string, string[]> = {
"agent-run-provenance.invalid.json": ["verifierResult"],
"app-cloud-manifest.invalid.json": ["cloudResources.0.ownerPackage", "dependencies", "forbiddenSharedRuntimes", "packageName"],
"integration-ref.invalid.json": ["uri"],
"no-cloud-evidence-pack.invalid.json": ["checks", "checks", "findings"],
Expand Down
Loading
Loading