cowork-harness 0.7.0 → 0.7.1

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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,19 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.7.1] — 2026-06-20
10
+
11
+ ### Fixed
12
+
13
+ - **`file_exists` and `user_visible_artifact` now pass for truncated (large) cassette artifacts.**
14
+ A truncated manifest entry (`truncated: true`) carries `path`, `bytes`, and `sha256` — positive
15
+ proof the file existed at record time. 0.7.0 incorrectly failed these existence/promotion
16
+ assertions with `"was truncated in the cassette — content was not committed; assertion cannot
17
+ pass"`, producing false-REDs for any cassette whose artifacts exceeded the 64 KiB inline cap.
18
+ Only **content** assertions (`artifact_json`) require the inlined body; existence assertions
19
+ now correctly pass from the manifest. `artifact_json` on a truncated artifact continues to fail
20
+ (the 0-byte placeholder is not valid JSON). Regression test added.
21
+
9
22
  ## [0.7.0] — 2026-06-19
10
23
 
11
24
  ### Added
package/dist/assert.js CHANGED
@@ -192,7 +192,9 @@ function check(a, ctx) {
192
192
  else {
193
193
  const relPath = relative(resolve(ctx.workRoot), abs);
194
194
  if (truncated.has(relPath)) {
195
- results.push(fail(`"${a.file_exists}" was truncated in the cassette — content was not committed; assertion cannot pass`));
195
+ // A truncated manifest entry carries path+bytes+sha256 — positive proof the file existed at
196
+ // record time. Existence is provable without the inlined body; only content assertions need it.
197
+ results.push(ok());
196
198
  }
197
199
  else {
198
200
  // Bug 37: verify the real path (after symlink resolution) is still under workRoot.
@@ -214,7 +216,12 @@ function check(a, ctx) {
214
216
  else {
215
217
  const rel = relative(resolve(ctx.workRoot), abs); // normalized, guaranteed under workRoot
216
218
  if (truncated.has(rel)) {
217
- results.push(fail(`"${p}" was truncated in the cassette — content was not committed; assertion cannot pass`));
219
+ // Truncated entry proves existence (path+bytes+sha256 recorded). Promotion is a path-prefix
220
+ // property — also knowable without the body. Pass if under a user-visible prefix.
221
+ const visible = ctx.userVisiblePrefixes.some((pre) => rel === pre || rel.startsWith(pre + "/"));
222
+ results.push(visible
223
+ ? ok()
224
+ : fail(`"${p}" is not under a user-visible prefix (${ctx.userVisiblePrefixes.join(", ")}) — invisible to the user in Cowork`));
218
225
  }
219
226
  else {
220
227
  const visible = ctx.userVisiblePrefixes.some((pre) => rel === pre || rel.startsWith(pre + "/"));
@@ -107,10 +107,11 @@ function decodeBody(e) {
107
107
  }
108
108
  /** #1: materialize a manifest into a temp work root so replay can run the filesystem assertions against it.
109
109
  * Small files get their inlined body (decoded per its encoding marker); hash-only (truncated)
110
- * files get an empty placeholder (file_exists FAILS; artifact_json on them fails loud — it needs the
111
- * body, which only small files carry). each path is containment-checked before writing so a hostile
112
- * cassette entry can't escape the temp root. every non-truncated body is verified against its
113
- * recorded sha256 (over the decoded RAW bytes) — a mismatch fails replay (throws). */
110
+ * files get an empty placeholder. A truncated entry carries path+bytes+sha256 — positive proof the
111
+ * file existed at record time — so file_exists and user_visible_artifact PASS from the manifest;
112
+ * only artifact_json fails loud (it needs the inlined body). each path is containment-checked before
113
+ * writing so a hostile cassette entry can't escape the temp root. every non-truncated body is verified
114
+ * against its recorded sha256 (over the decoded RAW bytes) — a mismatch fails replay (throws). */
114
115
  export function materializeManifest(entries) {
115
116
  const workRoot = mkdtempSync(join(tmpdir(), "cwh-replay-"));
116
117
  const truncatedPaths = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cowork-harness",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Scriptable, CI-friendly harness for Claude Cowork's runtime contract for testing skills across scenarios — same agent, mounts, egress allowlist, permission protocol, and sandbox limitations.",
5
5
  "license": "MIT",
6
6
  "type": "module",