@zhivex-ai/harness 1.4.0 → 1.5.0

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.
Files changed (128) hide show
  1. package/CHANGELOG.md +32 -10
  2. package/README.md +7 -5
  3. package/SUPPORT.md +2 -2
  4. package/contracts/client-protocol-v1.schema.json +593 -0
  5. package/contracts/engine-api.json +156 -0
  6. package/contracts/public-api.json +12 -0
  7. package/contracts/stable-api-signatures.json +36 -36
  8. package/dist/acp-cli.js +54 -20
  9. package/dist/approvals/approval-diff.d.ts +2 -1
  10. package/dist/approvals/approval-preview.d.ts +1 -1
  11. package/dist/audit/index.d.ts +3 -0
  12. package/dist/audit/index.js +482 -0
  13. package/dist/audit/pack.d.ts +72 -0
  14. package/dist/audit-pack-kvw7ng34.js +532 -0
  15. package/dist/cli/arguments.d.ts +3 -1
  16. package/dist/cli/audit-pack.d.ts +24 -0
  17. package/dist/cli/cli-credentials.d.ts +6 -0
  18. package/dist/cli/cli-profiles.d.ts +4 -2
  19. package/dist/cli/node-engine.d.ts +2 -0
  20. package/dist/cli-entry-75j3r49b.js +484 -0
  21. package/dist/cli-entry-928p8h41.js +10067 -0
  22. package/dist/cli-entry-h2yc9x37.js +10350 -0
  23. package/dist/{cli-entry-20w6nh0k.js → cli-entry-hdtfef27.js} +439 -19
  24. package/dist/{cli-entry-w0hterjs.js → cli-entry-q8bgkn9m.js} +40 -13
  25. package/dist/{cli-2h37qyvg.js → cli-vyrytn3m.js} +253 -644
  26. package/dist/cli.js +27 -2
  27. package/dist/client/json-contracts.d.ts +4 -0
  28. package/dist/client/protocol.d.ts +133 -0
  29. package/dist/client/task-control-host.d.ts +400 -0
  30. package/dist/client/task-projection-host.d.ts +12 -0
  31. package/dist/client/task-projection.d.ts +168 -0
  32. package/dist/compat/code-support.js +28 -22
  33. package/dist/{config-qt69hkqj.js → config-r9fdhce6.js} +3 -3
  34. package/dist/{config-j9624t6t.js → config-vyajmksy.js} +1 -1
  35. package/dist/engine/acp.js +15 -11
  36. package/dist/engine/client.js +14 -10
  37. package/dist/engine/desktop-providers-v1.d.ts +2 -0
  38. package/dist/engine/desktop-providers-v1.js +23 -3
  39. package/dist/engine/desktop-state-v1.js +11 -8
  40. package/dist/engine/index.d.ts +3 -0
  41. package/dist/engine/index.js +101 -554
  42. package/dist/engine/mcp-stdio-v1.js +8 -6
  43. package/dist/engine/models.js +76 -4
  44. package/dist/engine/protocol.d.ts +2 -0
  45. package/dist/engine/protocol.js +110 -0
  46. package/dist/engine/service.js +15 -11
  47. package/dist/hostile-repository-demo.js +1232 -564
  48. package/dist/{index-8zjmnyw5.js → index-1265j7t2.js} +73 -6
  49. package/dist/{index-nmjdwd4p.js → index-4gt045j0.js} +109 -1
  50. package/dist/{index-pdmevfxv.js → index-5zbq9ztj.js} +5 -259
  51. package/dist/{index-6dft2vev.js → index-63gbp0cz.js} +5 -3
  52. package/dist/index-6dxrbnxm.js +719 -0
  53. package/dist/{index-pp474137.js → index-6fp7kdpx.js} +769 -933
  54. package/dist/{index-61gthdz6.js → index-8sm2a1wa.js} +1 -1
  55. package/dist/{index-rkbf7dqg.js → index-96ezrvg2.js} +57 -13
  56. package/dist/index-bt2p8x7r.js +329 -0
  57. package/dist/index-c39197wq.js +552 -0
  58. package/dist/{index-m82cfspw.js → index-eps3c3g6.js} +877 -963
  59. package/dist/{index-s2k2edts.js → index-fqqz1yt8.js} +110 -1544
  60. package/dist/{index-m34q25jj.js → index-ggstwr0v.js} +192 -838
  61. package/dist/index-jpdvn09j.js +490 -0
  62. package/dist/{index-xqxeypt5.js → index-mzydbzp8.js} +76 -4
  63. package/dist/index-pq3kz1r8.js +269 -0
  64. package/dist/{index-wg1sd3at.js → index-qfsrrnz6.js} +1 -1
  65. package/dist/{index-937cb7y8.js → index-vc7yxz50.js} +561 -35
  66. package/dist/{index-5jhgatga.js → index-vvzc5ezz.js} +7 -6
  67. package/dist/index-wwthv10a.js +1507 -0
  68. package/dist/{index-tfzta544.js → index-xfedewpt.js} +17 -9
  69. package/dist/index-yx52bt4a.js +511 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.js +93 -77
  72. package/dist/integrations/mcp-stdio-tools.d.ts +4 -1
  73. package/dist/internal/desktop/providers.d.ts +2 -0
  74. package/dist/models/catalog.d.ts +2 -0
  75. package/dist/persistence/sqlite-database.d.ts +1 -0
  76. package/dist/providers/azure-openai.d.ts +14 -0
  77. package/dist/providers/bedrock.d.ts +23 -0
  78. package/dist/providers/providers.d.ts +1 -1
  79. package/dist/runtime/budget-ledger-scope.d.ts +14 -0
  80. package/dist/runtime/durable-review-group.d.ts +33 -0
  81. package/dist/runtime/harness.d.ts +11 -0
  82. package/dist/runtime/run-interruption.d.ts +7 -0
  83. package/dist/runtime/shared-budget.d.ts +30 -0
  84. package/dist/runtime/task-continuity.d.ts +300 -77
  85. package/dist/service-cli.js +15 -11
  86. package/dist/{service-client-cli-fn54ev6w.js → service-client-cli-v6qptg9x.js} +400 -305
  87. package/dist/tools/workspace.d.ts +2 -2
  88. package/dist/verify-historical-migrations.js +1237 -566
  89. package/dist/workspace/edit-contracts.d.ts +2 -2
  90. package/dist/workspace/path-policy.d.ts +2 -1
  91. package/dist/workspace/workspace.d.ts +13 -0
  92. package/dist/zhx.js +27 -2
  93. package/docs/AUDIT_PACK.md +99 -0
  94. package/docs/AZURE_OPENAI.md +53 -0
  95. package/docs/BEDROCK.md +56 -0
  96. package/docs/CLI.md +15 -0
  97. package/docs/CLIENT_PROTOCOL.md +178 -0
  98. package/docs/CODE_RELEASE.md +59 -7
  99. package/docs/CODE_WEB_TASKS.md +116 -0
  100. package/docs/CONTEXT_ENGINEERING.md +59 -0
  101. package/docs/CREDENTIALS.md +21 -0
  102. package/docs/DURABLE_OPERATIONS.md +93 -1
  103. package/docs/ENGINE_API.md +5 -2
  104. package/docs/EXTENSIBILITY.md +8 -4
  105. package/docs/FIRST_USE.md +6 -6
  106. package/docs/MINIMUM_TASK_AUTHORITY.md +3 -3
  107. package/docs/PROJECT_MEMORY.md +1 -1
  108. package/docs/PUBLIC_SECURITY.md +14 -1
  109. package/docs/README.md +6 -1
  110. package/docs/REPOSITORY_EDITING.md +1 -1
  111. package/docs/STABILITY.md +3 -1
  112. package/docs/SUPPORT_MATRIX.md +1 -1
  113. package/docs/SUPPORT_MATRIX_1_1.md +1 -1
  114. package/docs/TOOL_POLICY.md +4 -1
  115. package/docs/USAGE.md +3 -1
  116. package/docs/VERTEX.md +2 -2
  117. package/docs/WEB_LIMITS.md +4 -3
  118. package/docs/adr/0002-isolated-mcp-stdio.md +7 -4
  119. package/docs/support-matrix-1.1.json +3 -1
  120. package/docs/support-matrix.json +6 -4
  121. package/evaluations/code-task-ui.json +20 -0
  122. package/evaluations/representative-assembly-matrix.json +19 -1
  123. package/evaluations/task-projection.json +10 -0
  124. package/evaluations/task-recovery-lab.json +60 -0
  125. package/examples/README.md +3 -3
  126. package/package.json +23 -13
  127. package/dist/cli-entry-4ngptr62.js +0 -19900
  128. package/dist/index-m49jx72n.js +0 -220
@@ -1,23 +1,28 @@
1
1
  import {
2
2
  workspaceCheckpointSchema,
3
3
  workspaceRestoreOperationSchema
4
- } from "./index-6dft2vev.js";
4
+ } from "./index-63gbp0cz.js";
5
5
  import {
6
6
  HARNESS_OPERATIONS_SCHEMA_VERSION,
7
- HARNESS_SESSION_SCHEMA_VERSION,
8
7
  HARNESS_SQLITE_FILE,
9
- assertTaskBudgetBackupLinks,
10
8
  decodeProjectMemory,
11
- openCliSessionStore,
12
9
  openHarnessPersistence,
13
10
  projectMemoryRecordKey,
14
- sqliteAdapter,
11
+ sqliteAdapter
12
+ } from "./index-wwthv10a.js";
13
+ import {
14
+ BUDGET_LEDGER_NAMESPACE,
15
+ BUDGET_OWNER_NAMESPACE_KEY,
16
+ HARNESS_SESSION_SCHEMA_VERSION,
17
+ assertTaskBudgetBackupLinks,
18
+ budgetLedgerNamespace,
19
+ openCliSessionStore,
15
20
  validateStateDirectory
16
- } from "./index-s2k2edts.js";
21
+ } from "./index-fqqz1yt8.js";
17
22
  import {
18
23
  SqliteDatabase,
19
24
  readRegularFileChunksNoFollow
20
- } from "./index-m34q25jj.js";
25
+ } from "./index-6dxrbnxm.js";
21
26
  import {
22
27
  HarnessStateConflictError,
23
28
  HarnessWorkspaceError
@@ -396,7 +401,8 @@ var coordinatorIds = (runs) => new Set(runs.flatMap(({ state }) => {
396
401
  var validateBudgetLedger = (config, run, identities) => {
397
402
  const state = validatedRunState(run.state, `Budget ledger ${run.key}`);
398
403
  const identity = state.metadata?.budgetIdentity;
399
- if (state.scope?.namespace !== "__zhivex_budget__" || state.scope?.tenantId !== config.scope.tenantId || (state.scope?.userId ?? undefined) !== (config.scope.userId ?? undefined) || state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true || typeof identity !== "string" || !identities.has(identity) || !/^budget_[a-f0-9]{64}$/.test(state.runId) || !sdkRunKeyMatches(run.key, state.scope, state.runId)) {
404
+ const ownerNamespace = config.scope.namespace;
405
+ if (!ownerNamespace || state.scope?.namespace !== budgetLedgerNamespace(ownerNamespace) || state.metadata?.[BUDGET_OWNER_NAMESPACE_KEY] !== ownerNamespace || state.scope?.tenantId !== config.scope.tenantId || (state.scope?.userId ?? undefined) !== (config.scope.userId ?? undefined) || state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true || typeof identity !== "string" || !identities.has(identity) || !/^budget_[a-f0-9]{64}$/.test(state.runId) || !sdkRunKeyMatches(run.key, state.scope, state.runId)) {
400
406
  throw new HarnessStateConflictError("Budget ledger is not bound to an exported run and durable scope.");
401
407
  }
402
408
  assertTerminalState(state, `Budget ledger ${run.key}`);
@@ -530,7 +536,9 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
530
536
  const metadata = state.metadata;
531
537
  if (typeof metadata?.budgetIdentity !== "string" || !identities.has(metadata.budgetIdentity)) {
532
538
  const scope = state.scope;
533
- if (scope?.namespace === "__zhivex_budget__" && scope.tenantId === config.scope.tenantId && (scope.userId ?? undefined) === (config.scope.userId ?? undefined)) {
539
+ const sameTenant = scope?.tenantId === config.scope.tenantId && (scope?.userId ?? undefined) === (config.scope.userId ?? undefined);
540
+ const boundNamespace = config.scope.namespace ? budgetLedgerNamespace(config.scope.namespace) : undefined;
541
+ if (sameTenant && (scope?.namespace === BUDGET_LEDGER_NAMESPACE || scope?.namespace === boundNamespace)) {
534
542
  throw new HarnessStateConflictError("An unlinked shared budget ledger cannot be safely assigned to this backup scope; retain the original database.");
535
543
  }
536
544
  return [];
@@ -0,0 +1,511 @@
1
+ import {
2
+ fileDigestSchema
3
+ } from "./index-6dxrbnxm.js";
4
+
5
+ // src/workspace/change-envelope.ts
6
+ import { createHash } from "node:crypto";
7
+ import { z } from "zod";
8
+ var CHANGE_ENVELOPE_SCHEMA_VERSION = 1;
9
+ var CHANGE_ENVELOPE_DIGEST_ALGORITHM = "sha256";
10
+ var MAX_CHANGE_ENVELOPE_CHECKS = 100;
11
+ var MAX_CHANGE_ENVELOPE_APPROVALS = 100;
12
+ var MAX_CHANGE_ENVELOPE_APPROVAL_SCOPES = 32;
13
+ var MAX_CHANGE_ENVELOPE_ATTESTATIONS = 32;
14
+ var timestampSchema = z.iso.datetime({ precision: 3 });
15
+ var boundedIdentifierSchema = z.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/, "Identifier must contain only bounded non-secret token characters.");
16
+ var approvalScopeSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:/*-]*$/, "Approval scope must contain only bounded token characters.");
17
+ var attestationFormatSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9.+_-]*$/);
18
+ var mediaTypeSchema = z.string().min(3).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/);
19
+ var addDuplicateIssues = (values, key, context, path) => {
20
+ const seen = new Set;
21
+ for (let index = 0;index < values.length; index += 1) {
22
+ const value = values[index];
23
+ if (value === undefined)
24
+ continue;
25
+ const identifier = key(value);
26
+ if (seen.has(identifier)) {
27
+ context.addIssue({
28
+ code: "custom",
29
+ path: [index, path],
30
+ message: `Duplicate ${path}: ${identifier}.`
31
+ });
32
+ }
33
+ seen.add(identifier);
34
+ }
35
+ };
36
+ var addCanonicalOrderIssue = (values, key, context, path) => {
37
+ for (let index = 1;index < values.length; index += 1) {
38
+ const previous = values[index - 1];
39
+ const current = values[index];
40
+ if (previous !== undefined && current !== undefined && key(previous) >= key(current)) {
41
+ context.addIssue({
42
+ code: "custom",
43
+ path: [index, path],
44
+ message: `${path} entries must be unique and in canonical ascending order.`
45
+ });
46
+ }
47
+ }
48
+ };
49
+ var approvalScopesInputSchema = z.array(approvalScopeSchema).min(1).max(MAX_CHANGE_ENVELOPE_APPROVAL_SCOPES).superRefine((scopes, context) => addDuplicateIssues(scopes, (scope) => scope, context, "scope"));
50
+ var canonicalApprovalScopesSchema = z.array(approvalScopeSchema).min(1).max(MAX_CHANGE_ENVELOPE_APPROVAL_SCOPES).superRefine((scopes, context) => addCanonicalOrderIssue(scopes, (scope) => scope, context, "scope"));
51
+ var changeEnvelopeCheckSchema = z.strictObject({
52
+ checkId: boundedIdentifierSchema,
53
+ status: z.enum(["passed", "failed", "skipped"]),
54
+ redacted: z.literal(true),
55
+ startedAt: timestampSchema,
56
+ completedAt: timestampSchema,
57
+ durationMs: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
58
+ exitCode: z.number().int().min(0).max(255).optional(),
59
+ redactedOutputDigest: fileDigestSchema.optional(),
60
+ runnerFingerprint: fileDigestSchema.optional()
61
+ }).superRefine((check, context) => {
62
+ const startedAt = Date.parse(check.startedAt);
63
+ const completedAt = Date.parse(check.completedAt);
64
+ if (completedAt < startedAt) {
65
+ context.addIssue({
66
+ code: "custom",
67
+ path: ["completedAt"],
68
+ message: "Check completion cannot precede its start."
69
+ });
70
+ }
71
+ if (check.status === "passed" && check.exitCode !== undefined && check.exitCode !== 0) {
72
+ context.addIssue({
73
+ code: "custom",
74
+ path: ["exitCode"],
75
+ message: "A passed check cannot have a non-zero exit code."
76
+ });
77
+ }
78
+ if (check.status === "failed" && check.exitCode === 0) {
79
+ context.addIssue({
80
+ code: "custom",
81
+ path: ["exitCode"],
82
+ message: "A failed check cannot have a zero exit code."
83
+ });
84
+ }
85
+ if (check.status === "skipped" && check.exitCode !== undefined) {
86
+ context.addIssue({
87
+ code: "custom",
88
+ path: ["exitCode"],
89
+ message: "A skipped check cannot have an exit code."
90
+ });
91
+ }
92
+ });
93
+ var checkInputListSchema = z.array(changeEnvelopeCheckSchema).min(1).max(MAX_CHANGE_ENVELOPE_CHECKS).superRefine((checks, context) => addDuplicateIssues(checks, (check) => check.checkId, context, "checkId"));
94
+ var canonicalCheckListSchema = z.array(changeEnvelopeCheckSchema).min(1).max(MAX_CHANGE_ENVELOPE_CHECKS).superRefine((checks, context) => addCanonicalOrderIssue(checks, (check) => check.checkId, context, "checkId"));
95
+ var changeEnvelopeApprovalSchema = z.strictObject({
96
+ kind: z.literal("approval-reference"),
97
+ approvalId: boundedIdentifierSchema,
98
+ approvalDigest: fileDigestSchema,
99
+ approverDigest: fileDigestSchema,
100
+ scopes: canonicalApprovalScopesSchema,
101
+ approvedAt: timestampSchema,
102
+ expiresAt: timestampSchema.optional()
103
+ }).superRefine((approval, context) => {
104
+ if (approval.expiresAt && Date.parse(approval.expiresAt) <= Date.parse(approval.approvedAt)) {
105
+ context.addIssue({
106
+ code: "custom",
107
+ path: ["expiresAt"],
108
+ message: "Approval expiry must be later than approval time."
109
+ });
110
+ }
111
+ });
112
+ var changeEnvelopeApprovalInputSchema = z.strictObject({
113
+ kind: z.literal("approval-reference"),
114
+ approvalId: boundedIdentifierSchema,
115
+ approvalDigest: fileDigestSchema,
116
+ approverDigest: fileDigestSchema,
117
+ scopes: approvalScopesInputSchema,
118
+ approvedAt: timestampSchema,
119
+ expiresAt: timestampSchema.optional()
120
+ }).superRefine((approval, context) => {
121
+ if (approval.expiresAt && Date.parse(approval.expiresAt) <= Date.parse(approval.approvedAt)) {
122
+ context.addIssue({
123
+ code: "custom",
124
+ path: ["expiresAt"],
125
+ message: "Approval expiry must be later than approval time."
126
+ });
127
+ }
128
+ });
129
+ var approvalInputListSchema = z.array(changeEnvelopeApprovalInputSchema).min(1).max(MAX_CHANGE_ENVELOPE_APPROVALS).superRefine((approvals, context) => addDuplicateIssues(approvals, (approval) => approval.approvalId, context, "approvalId"));
130
+ var canonicalApprovalListSchema = z.array(changeEnvelopeApprovalSchema).min(1).max(MAX_CHANGE_ENVELOPE_APPROVALS).superRefine((approvals, context) => addCanonicalOrderIssue(approvals, (approval) => approval.approvalId, context, "approvalId"));
131
+ var externalAttestationReferenceSchema = z.strictObject({
132
+ kind: z.literal("external-attestation-reference"),
133
+ attestationId: boundedIdentifierSchema,
134
+ format: attestationFormatSchema,
135
+ mediaType: mediaTypeSchema,
136
+ bundleDigest: fileDigestSchema
137
+ });
138
+ var externalAttestationInputListSchema = z.array(externalAttestationReferenceSchema).min(1).max(MAX_CHANGE_ENVELOPE_ATTESTATIONS).superRefine((attestations, context) => addDuplicateIssues(attestations, (attestation) => attestation.attestationId, context, "attestationId"));
139
+ var canonicalExternalAttestationListSchema = z.array(externalAttestationReferenceSchema).min(1).max(MAX_CHANGE_ENVELOPE_ATTESTATIONS).superRefine((attestations, context) => addCanonicalOrderIssue(attestations, (attestation) => attestation.attestationId, context, "attestationId"));
140
+ var changeEnvelopeBaseSchema = z.strictObject({
141
+ workspaceDigest: fileDigestSchema,
142
+ treeDigest: fileDigestSchema
143
+ });
144
+ var changeEnvelopePatchSchema = z.strictObject({
145
+ patchId: boundedIdentifierSchema,
146
+ patchDigest: fileDigestSchema
147
+ });
148
+ var changeEnvelopeFingerprintsSchema = z.strictObject({
149
+ harness: fileDigestSchema,
150
+ policy: fileDigestSchema,
151
+ environment: fileDigestSchema
152
+ });
153
+ var changeEnvelopeEvidenceSchema = z.strictObject({
154
+ schemaVersion: z.literal(CHANGE_ENVELOPE_SCHEMA_VERSION),
155
+ kind: z.literal("check-evidence-summary"),
156
+ digestAlgorithm: z.literal(CHANGE_ENVELOPE_DIGEST_ALGORITHM),
157
+ evidenceDigest: fileDigestSchema,
158
+ checks: canonicalCheckListSchema
159
+ });
160
+ var changeEnvelopeInputSchema = z.strictObject({
161
+ createdAt: timestampSchema,
162
+ expiresAt: timestampSchema.optional(),
163
+ base: changeEnvelopeBaseSchema,
164
+ patch: changeEnvelopePatchSchema,
165
+ fingerprints: changeEnvelopeFingerprintsSchema,
166
+ checks: checkInputListSchema,
167
+ approvals: approvalInputListSchema.optional(),
168
+ externalAttestations: externalAttestationInputListSchema.optional()
169
+ });
170
+ var changeEnvelopeSchema = z.strictObject({
171
+ schemaVersion: z.literal(CHANGE_ENVELOPE_SCHEMA_VERSION),
172
+ kind: z.literal("change-envelope"),
173
+ digestAlgorithm: z.literal(CHANGE_ENVELOPE_DIGEST_ALGORITHM),
174
+ envelopeId: fileDigestSchema,
175
+ createdAt: timestampSchema,
176
+ expiresAt: timestampSchema.optional(),
177
+ base: changeEnvelopeBaseSchema,
178
+ patch: changeEnvelopePatchSchema,
179
+ fingerprints: changeEnvelopeFingerprintsSchema,
180
+ evidence: changeEnvelopeEvidenceSchema,
181
+ approvals: canonicalApprovalListSchema.optional(),
182
+ externalAttestations: canonicalExternalAttestationListSchema.optional()
183
+ }).superRefine((envelope, context) => {
184
+ if (envelope.expiresAt && Date.parse(envelope.expiresAt) <= Date.parse(envelope.createdAt)) {
185
+ context.addIssue({
186
+ code: "custom",
187
+ path: ["expiresAt"],
188
+ message: "Envelope expiry must be later than creation time."
189
+ });
190
+ }
191
+ for (let index = 0;index < envelope.evidence.checks.length; index += 1) {
192
+ const check = envelope.evidence.checks[index];
193
+ if (check && Date.parse(check.completedAt) > Date.parse(envelope.createdAt)) {
194
+ context.addIssue({
195
+ code: "custom",
196
+ path: ["evidence", "checks", index, "completedAt"],
197
+ message: "Check completion cannot be later than envelope creation time."
198
+ });
199
+ }
200
+ }
201
+ for (let index = 0;index < (envelope.approvals?.length ?? 0); index += 1) {
202
+ const approval = envelope.approvals?.[index];
203
+ if (approval && Date.parse(approval.approvedAt) > Date.parse(envelope.createdAt)) {
204
+ context.addIssue({
205
+ code: "custom",
206
+ path: ["approvals", index, "approvedAt"],
207
+ message: "Approval time cannot be later than envelope creation time."
208
+ });
209
+ }
210
+ }
211
+ });
212
+ var canonicalJson = (value) => {
213
+ if (value === null || typeof value === "boolean" || typeof value === "string") {
214
+ return JSON.stringify(value);
215
+ }
216
+ if (typeof value === "number") {
217
+ if (!Number.isFinite(value))
218
+ throw new TypeError("Canonical JSON cannot encode a non-finite number.");
219
+ return JSON.stringify(Object.is(value, -0) ? 0 : value);
220
+ }
221
+ if (Array.isArray(value)) {
222
+ return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
223
+ }
224
+ if (typeof value === "object") {
225
+ const record = value;
226
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
227
+ }
228
+ throw new TypeError(`Canonical JSON cannot encode ${typeof value}.`);
229
+ };
230
+ var digestCanonical = (value) => `sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
231
+ var digestChangeEnvelopeArtifact = (value) => `sha256:${createHash("sha256").update(value).digest("hex")}`;
232
+ var evidencePayload = (checks) => ({
233
+ schemaVersion: CHANGE_ENVELOPE_SCHEMA_VERSION,
234
+ kind: "check-evidence-summary",
235
+ digestAlgorithm: CHANGE_ENVELOPE_DIGEST_ALGORITHM,
236
+ checks: [...checks]
237
+ });
238
+ var envelopePayload = (envelope) => {
239
+ const { envelopeId: _envelopeId, ...payload } = envelope;
240
+ return payload;
241
+ };
242
+ var sortBy = (values, key) => [...values].sort((left, right) => {
243
+ const leftKey = key(left);
244
+ const rightKey = key(right);
245
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
246
+ });
247
+ var createChangeEnvelope = (input) => {
248
+ const parsed = changeEnvelopeInputSchema.parse(input);
249
+ const checks = sortBy(parsed.checks, (check) => check.checkId);
250
+ const evidenceWithoutDigest = evidencePayload(checks);
251
+ const evidence = {
252
+ ...evidenceWithoutDigest,
253
+ evidenceDigest: digestCanonical(evidenceWithoutDigest)
254
+ };
255
+ const approvals = parsed.approvals ? sortBy(parsed.approvals, (approval) => approval.approvalId).map((approval) => ({
256
+ ...approval,
257
+ scopes: [...approval.scopes].sort()
258
+ })) : undefined;
259
+ const externalAttestations = parsed.externalAttestations ? sortBy(parsed.externalAttestations, (attestation) => attestation.attestationId) : undefined;
260
+ const payload = {
261
+ schemaVersion: CHANGE_ENVELOPE_SCHEMA_VERSION,
262
+ kind: "change-envelope",
263
+ digestAlgorithm: CHANGE_ENVELOPE_DIGEST_ALGORITHM,
264
+ createdAt: parsed.createdAt,
265
+ ...parsed.expiresAt ? { expiresAt: parsed.expiresAt } : {},
266
+ base: parsed.base,
267
+ patch: parsed.patch,
268
+ fingerprints: parsed.fingerprints,
269
+ evidence,
270
+ ...approvals ? { approvals } : {},
271
+ ...externalAttestations ? { externalAttestations } : {}
272
+ };
273
+ return changeEnvelopeSchema.parse({
274
+ ...payload,
275
+ envelopeId: digestCanonical(payload)
276
+ });
277
+ };
278
+ var canonicalizeChangeEnvelope = (input) => canonicalJson(changeEnvelopeSchema.parse(input));
279
+ var computeChangeEnvelopeDigest = (input) => {
280
+ const parsed = changeEnvelopeSchema.parse(input);
281
+ return digestCanonical(envelopePayload(parsed));
282
+ };
283
+ var computeChangeEnvelopeEvidenceDigest = (input) => {
284
+ const parsed = changeEnvelopeEvidenceSchema.parse(input);
285
+ return digestCanonical(evidencePayload(parsed.checks));
286
+ };
287
+ var changeEnvelopePreconditionsSchema = z.strictObject({
288
+ envelopeId: fileDigestSchema.optional(),
289
+ baseWorkspaceDigest: fileDigestSchema.optional(),
290
+ baseTreeDigest: fileDigestSchema.optional(),
291
+ patchId: boundedIdentifierSchema.optional(),
292
+ patchDigest: fileDigestSchema.optional(),
293
+ harnessFingerprint: fileDigestSchema.optional(),
294
+ policyFingerprint: fileDigestSchema.optional(),
295
+ environmentFingerprint: fileDigestSchema.optional(),
296
+ evidenceDigest: fileDigestSchema.optional(),
297
+ requiredPassedChecks: z.array(boundedIdentifierSchema).max(MAX_CHANGE_ENVELOPE_CHECKS).optional(),
298
+ requiredApprovalDigests: z.array(fileDigestSchema).max(MAX_CHANGE_ENVELOPE_APPROVALS).optional(),
299
+ requiredApprovalScopes: z.array(approvalScopeSchema).max(MAX_CHANGE_ENVELOPE_APPROVAL_SCOPES).optional(),
300
+ requiredAttestationDigests: z.array(fileDigestSchema).max(MAX_CHANGE_ENVELOPE_ATTESTATIONS).optional()
301
+ }).superRefine((preconditions, context) => {
302
+ if (preconditions.requiredPassedChecks) {
303
+ addDuplicateIssues(preconditions.requiredPassedChecks, (checkId) => checkId, context, "requiredPassedCheck");
304
+ }
305
+ if (preconditions.requiredApprovalDigests) {
306
+ addDuplicateIssues(preconditions.requiredApprovalDigests, (digest) => digest, context, "requiredApprovalDigest");
307
+ }
308
+ if (preconditions.requiredApprovalScopes) {
309
+ addDuplicateIssues(preconditions.requiredApprovalScopes, (scope) => scope, context, "requiredApprovalScope");
310
+ }
311
+ if (preconditions.requiredAttestationDigests) {
312
+ addDuplicateIssues(preconditions.requiredAttestationDigests, (digest) => digest, context, "requiredAttestationDigest");
313
+ }
314
+ });
315
+ var verificationOptionsSchema = z.strictObject({
316
+ now: z.union([z.date(), z.number().int(), timestampSchema]).optional(),
317
+ preconditions: changeEnvelopePreconditionsSchema.optional()
318
+ });
319
+ var verificationTime = (value) => {
320
+ const date = value === undefined ? new Date : value instanceof Date ? value : new Date(value);
321
+ if (!Number.isFinite(date.getTime()))
322
+ throw new TypeError("Verification time must be a valid date.");
323
+ return date;
324
+ };
325
+ var verifyChangeEnvelope = (input, options = {}) => {
326
+ const parsedOptions = verificationOptionsSchema.parse(options);
327
+ const now = verificationTime(parsedOptions.now);
328
+ const checkedAt = now.toISOString();
329
+ const parsedEnvelope = changeEnvelopeSchema.safeParse(input);
330
+ if (!parsedEnvelope.success) {
331
+ return {
332
+ valid: false,
333
+ verificationScope: "integrity-expiration-and-preconditions-only",
334
+ issues: parsedEnvelope.error.issues.map((issue) => ({
335
+ code: "invalid-schema",
336
+ path: issue.path.map((segment) => typeof segment === "symbol" ? segment.description ?? "symbol" : segment),
337
+ message: issue.message
338
+ })),
339
+ integrity: { envelopeDigestValid: false, evidenceDigestValid: false },
340
+ expiration: { checkedAt, envelopeNotYetValid: false, envelopeExpired: false, expiredApprovalIds: [] },
341
+ checks: {
342
+ recorded: 0,
343
+ passed: [],
344
+ failed: [],
345
+ skipped: [],
346
+ requiredPassed: [...parsedOptions.preconditions?.requiredPassedChecks ?? []].sort(),
347
+ missingPassed: [...parsedOptions.preconditions?.requiredPassedChecks ?? []].sort()
348
+ },
349
+ approvals: {
350
+ recorded: 0,
351
+ requiredScopes: [...parsedOptions.preconditions?.requiredApprovalScopes ?? []].sort(),
352
+ missingScopes: [...parsedOptions.preconditions?.requiredApprovalScopes ?? []].sort(),
353
+ authenticity: "not-verified"
354
+ },
355
+ externalAttestations: {
356
+ referenced: 0,
357
+ requiredDigests: [...parsedOptions.preconditions?.requiredAttestationDigests ?? []].sort(),
358
+ missingDigests: [...parsedOptions.preconditions?.requiredAttestationDigests ?? []].sort(),
359
+ authenticity: "not-verified"
360
+ }
361
+ };
362
+ }
363
+ const envelope = parsedEnvelope.data;
364
+ const issues = [];
365
+ const computedEvidenceDigest = digestCanonical(evidencePayload(envelope.evidence.checks));
366
+ const evidenceDigestValid = computedEvidenceDigest === envelope.evidence.evidenceDigest;
367
+ if (!evidenceDigestValid) {
368
+ issues.push({
369
+ code: "evidence-digest-mismatch",
370
+ path: ["evidence", "evidenceDigest"],
371
+ message: "Evidence digest does not match the canonical redacted check summary.",
372
+ expected: computedEvidenceDigest,
373
+ actual: envelope.evidence.evidenceDigest
374
+ });
375
+ }
376
+ const computedEnvelopeDigest = digestCanonical(envelopePayload(envelope));
377
+ const envelopeDigestValid = computedEnvelopeDigest === envelope.envelopeId;
378
+ if (!envelopeDigestValid) {
379
+ issues.push({
380
+ code: "envelope-digest-mismatch",
381
+ path: ["envelopeId"],
382
+ message: "Envelope digest does not match its canonical payload.",
383
+ expected: computedEnvelopeDigest,
384
+ actual: envelope.envelopeId
385
+ });
386
+ }
387
+ const envelopeNotYetValid = now.getTime() < Date.parse(envelope.createdAt);
388
+ if (envelopeNotYetValid) {
389
+ issues.push({
390
+ code: "envelope-not-yet-valid",
391
+ path: ["createdAt"],
392
+ message: "Change envelope creation time is in the future."
393
+ });
394
+ }
395
+ const envelopeExpired = envelope.expiresAt !== undefined && now.getTime() >= Date.parse(envelope.expiresAt);
396
+ if (envelopeExpired) {
397
+ issues.push({
398
+ code: "envelope-expired",
399
+ path: ["expiresAt"],
400
+ message: "Change envelope has expired."
401
+ });
402
+ }
403
+ const expiredApprovalIds = (envelope.approvals ?? []).filter((approval) => approval.expiresAt !== undefined && now.getTime() >= Date.parse(approval.expiresAt)).map((approval) => approval.approvalId);
404
+ for (const approvalId of expiredApprovalIds) {
405
+ issues.push({
406
+ code: "approval-expired",
407
+ path: ["approvals", envelope.approvals?.findIndex((approval) => approval.approvalId === approvalId) ?? 0, "expiresAt"],
408
+ message: `Recorded approval has expired: ${approvalId}.`
409
+ });
410
+ }
411
+ const preconditions = parsedOptions.preconditions;
412
+ const comparePrecondition = (field, expected, actual, path) => {
413
+ if (expected !== undefined && expected !== actual) {
414
+ issues.push({
415
+ code: "precondition-mismatch",
416
+ path,
417
+ message: `Change envelope precondition does not match: ${field}.`,
418
+ expected,
419
+ actual
420
+ });
421
+ }
422
+ };
423
+ comparePrecondition("envelopeId", preconditions?.envelopeId, envelope.envelopeId, ["envelopeId"]);
424
+ comparePrecondition("baseWorkspaceDigest", preconditions?.baseWorkspaceDigest, envelope.base.workspaceDigest, ["base", "workspaceDigest"]);
425
+ comparePrecondition("baseTreeDigest", preconditions?.baseTreeDigest, envelope.base.treeDigest, ["base", "treeDigest"]);
426
+ comparePrecondition("patchId", preconditions?.patchId, envelope.patch.patchId, ["patch", "patchId"]);
427
+ comparePrecondition("patchDigest", preconditions?.patchDigest, envelope.patch.patchDigest, ["patch", "patchDigest"]);
428
+ comparePrecondition("harnessFingerprint", preconditions?.harnessFingerprint, envelope.fingerprints.harness, ["fingerprints", "harness"]);
429
+ comparePrecondition("policyFingerprint", preconditions?.policyFingerprint, envelope.fingerprints.policy, ["fingerprints", "policy"]);
430
+ comparePrecondition("environmentFingerprint", preconditions?.environmentFingerprint, envelope.fingerprints.environment, ["fingerprints", "environment"]);
431
+ comparePrecondition("evidenceDigest", preconditions?.evidenceDigest, envelope.evidence.evidenceDigest, ["evidence", "evidenceDigest"]);
432
+ const passedChecks = envelope.evidence.checks.filter((check) => check.status === "passed").map((check) => check.checkId);
433
+ const failedChecks = envelope.evidence.checks.filter((check) => check.status === "failed").map((check) => check.checkId);
434
+ const skippedChecks = envelope.evidence.checks.filter((check) => check.status === "skipped").map((check) => check.checkId);
435
+ const passedCheckSet = new Set(passedChecks);
436
+ const requiredPassed = [...preconditions?.requiredPassedChecks ?? []].sort();
437
+ const missingPassed = requiredPassed.filter((checkId) => !passedCheckSet.has(checkId));
438
+ for (const checkId of missingPassed) {
439
+ issues.push({
440
+ code: "missing-passed-check",
441
+ path: ["evidence", "checks"],
442
+ message: `Required passed check is not recorded: ${checkId}.`,
443
+ expected: checkId
444
+ });
445
+ }
446
+ const expiredApprovals = new Set(expiredApprovalIds);
447
+ const activeApprovals = (envelope.approvals ?? []).filter((approval) => !expiredApprovals.has(approval.approvalId));
448
+ const approvalDigests = new Set(activeApprovals.map((approval) => approval.approvalDigest));
449
+ for (const requiredDigest of preconditions?.requiredApprovalDigests ?? []) {
450
+ if (!approvalDigests.has(requiredDigest)) {
451
+ issues.push({
452
+ code: "missing-approval-digest",
453
+ path: ["approvals"],
454
+ message: `Required active approval digest is not recorded: ${requiredDigest}.`,
455
+ expected: requiredDigest
456
+ });
457
+ }
458
+ }
459
+ const approvedScopes = new Set(activeApprovals.flatMap((approval) => approval.scopes));
460
+ const requiredScopes = [...preconditions?.requiredApprovalScopes ?? []].sort();
461
+ const missingScopes = requiredScopes.filter((scope) => !approvedScopes.has(scope));
462
+ for (const scope of missingScopes) {
463
+ issues.push({
464
+ code: "missing-approval-scope",
465
+ path: ["approvals"],
466
+ message: `Required active approval scope is not recorded: ${scope}.`,
467
+ expected: scope
468
+ });
469
+ }
470
+ const attestationDigests = new Set((envelope.externalAttestations ?? []).map((attestation) => attestation.bundleDigest));
471
+ const requiredAttestationDigests = [...preconditions?.requiredAttestationDigests ?? []].sort();
472
+ const missingAttestationDigests = requiredAttestationDigests.filter((digest) => !attestationDigests.has(digest));
473
+ for (const requiredDigest of missingAttestationDigests) {
474
+ issues.push({
475
+ code: "missing-attestation-digest",
476
+ path: ["externalAttestations"],
477
+ message: `Required external attestation digest is not referenced: ${requiredDigest}.`,
478
+ expected: requiredDigest
479
+ });
480
+ }
481
+ return {
482
+ valid: issues.length === 0,
483
+ verificationScope: "integrity-expiration-and-preconditions-only",
484
+ envelope,
485
+ issues,
486
+ integrity: { envelopeDigestValid, evidenceDigestValid },
487
+ expiration: { checkedAt, envelopeNotYetValid, envelopeExpired, expiredApprovalIds },
488
+ checks: {
489
+ recorded: envelope.evidence.checks.length,
490
+ passed: passedChecks,
491
+ failed: failedChecks,
492
+ skipped: skippedChecks,
493
+ requiredPassed,
494
+ missingPassed
495
+ },
496
+ approvals: {
497
+ recorded: envelope.approvals?.length ?? 0,
498
+ requiredScopes,
499
+ missingScopes,
500
+ authenticity: "not-verified"
501
+ },
502
+ externalAttestations: {
503
+ referenced: envelope.externalAttestations?.length ?? 0,
504
+ requiredDigests: requiredAttestationDigests,
505
+ missingDigests: missingAttestationDigests,
506
+ authenticity: "not-verified"
507
+ }
508
+ };
509
+ };
510
+
511
+ export { CHANGE_ENVELOPE_SCHEMA_VERSION, CHANGE_ENVELOPE_DIGEST_ALGORITHM, MAX_CHANGE_ENVELOPE_CHECKS, MAX_CHANGE_ENVELOPE_APPROVALS, MAX_CHANGE_ENVELOPE_APPROVAL_SCOPES, MAX_CHANGE_ENVELOPE_ATTESTATIONS, changeEnvelopeCheckSchema, changeEnvelopeApprovalSchema, externalAttestationReferenceSchema, changeEnvelopeBaseSchema, changeEnvelopePatchSchema, changeEnvelopeFingerprintsSchema, changeEnvelopeEvidenceSchema, changeEnvelopeInputSchema, changeEnvelopeSchema, digestChangeEnvelopeArtifact, createChangeEnvelope, canonicalizeChangeEnvelope, computeChangeEnvelopeDigest, computeChangeEnvelopeEvidenceDigest, changeEnvelopePreconditionsSchema, verifyChangeEnvelope };
package/dist/index.d.ts CHANGED
@@ -60,3 +60,6 @@ export { serveAcpStdio } from "./client/acp-stdio.js";
60
60
  export { openWorkspaceCheckpointStore } from "./persistence/workspace-checkpoints.js";
61
61
  export type { WorkspaceCheckpoint, WorkspaceRestoreOperation } from "./persistence/workspace-checkpoints.js";
62
62
  export type { HarnessMcpResourceClient } from "./integrations/mcp.js";
63
+ export { runHarnessDurableReviewGroup, inspectHarnessReviewGroup, cancelHarnessReviewGroup } from "./runtime/durable-review-group.js";
64
+ export type { HarnessDurableReviewGroupResult } from "./runtime/durable-review-group.js";
65
+ export type { HarnessSharedBudgetOptions } from "./runtime/shared-budget.js";