@crewhaus/tool-lifecycle 0.8.0 → 0.8.2
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/README.md +6 -1
- package/dist/index.js +16 -20
- package/dist/lib/result.d.ts +5 -1
- package/dist/lib/result.js +11 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -140,7 +140,12 @@ verified.
|
|
|
140
140
|
logs, the tool results the runtime saved to a file, or its proof bundles,
|
|
141
141
|
as `to: .crewhaus` would — with no override and in every permission mode
|
|
142
142
|
(0.8.0). The provability gate and `continuity.proof` read that record as
|
|
143
|
-
evidence, so a migration onto it could rewrite the evidence
|
|
143
|
+
evidence, so a migration onto it could rewrite the evidence. A copy that
|
|
144
|
+
would land in the files that control the run (its run files, settings, gate
|
|
145
|
+
records, environment files, compiled bundle and dependencies, Hangar's
|
|
146
|
+
directory), which decide what the agent
|
|
147
|
+
may do next, is refused the
|
|
148
|
+
same way;
|
|
144
149
|
- a `targetVersion` different from the source's, and any `targetVersion` at all
|
|
145
150
|
against a store with no version stamp. See below.
|
|
146
151
|
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ import { existsSync, lstatSync, readdirSync, statSync } from "node:fs";
|
|
|
50
50
|
import * as path from "node:path";
|
|
51
51
|
import { AUDIT_CHAIN_EXCLUSION_REASON, InvalidRetentionDateError, KNOWLEDGE_MARKER_RELPATH, RETENTION_CONFIG_RELPATH, RETIREMENT_LOG_FILENAME, RetentionConfigError, RetireError, SHARED_DIR_DEFAULT, applyPull, applyPush, buildRetirementPlan, formatPlan, fragmentContentHash, harnessOptedIn, openHarnessRecordStore, parseRetentionDate, planPull, planPush, readHarnessGraders, readHarnessMemories, readHarnessPrompts, runRetentionExport, runRetentionPurge, runRetentionSweep, runRetirement, } from "@crewhaus/harness-lifecycle";
|
|
52
52
|
import { buildTool } from "@crewhaus/tool-builder";
|
|
53
|
-
import {
|
|
53
|
+
import { protectedRootBeneath, protectedRootsOf, protectedWriteRefusal, } from "@crewhaus/tool-safety/fs";
|
|
54
54
|
import { z } from "zod";
|
|
55
55
|
import { NO_PII_PASS_REASON, PULLED_MEMORIES_RELPATH, SHARED_MANIFEST_RELPATH, SHARED_MEMORIES_RELPATH, auditHarnessMemoryFiles, auditSharedFragments, auditSharedMemories, harnessMemoryHashes, landedSharedGraderHashes, makeRedactor, pulledFragmentRelPath, sharedFragmentRelPath, } from "./lib/knowledge";
|
|
56
56
|
import { COVERED_STORE_DIRS, EXPORT_MANIFEST_FILENAME, RECEIPT_FILENAME, RECORD_SHAPE_MIGRATION_UNAVAILABLE, inspectDestination, plannedPaths, readStoreVersion, uncoveredStateEntries, writeReceipt, writtenPaths, } from "./lib/migrate";
|
|
@@ -189,7 +189,7 @@ export const harnessRetire = buildTool({
|
|
|
189
189
|
}),
|
|
190
190
|
destructive: true,
|
|
191
191
|
requireJustification: true,
|
|
192
|
-
execute: async (input) => {
|
|
192
|
+
execute: async (input, ctx) => {
|
|
193
193
|
const tool = "HarnessRetire";
|
|
194
194
|
const dryRun = input.dryRun ?? true;
|
|
195
195
|
if (!specNameIsSafe(input.spec)) {
|
|
@@ -206,6 +206,11 @@ export const harnessRetire = buildTool({
|
|
|
206
206
|
if (!registry.ok)
|
|
207
207
|
return refusal(tool, registry.code, registry.reason);
|
|
208
208
|
const stateDir = path.join(harness.value.real, STATE_RELDIR);
|
|
209
|
+
// Retiring moves the state directory away, so a run cannot retire the harness
|
|
210
|
+
// it runs in. Refused for a preview too: it says what a real run would do.
|
|
211
|
+
if (protectedRootBeneath(stateDir, protectedRootsOf(ctx)) !== undefined) {
|
|
212
|
+
return protectedWriteRefusal(tool, `retiring ${dirRel}`);
|
|
213
|
+
}
|
|
209
214
|
if (overlaps(archive.value.real, stateDir)) {
|
|
210
215
|
return refusal(tool, "refused", `the archive directory and the state directory ${STATE_RELDIR} overlap — archiving a directory into itself would destroy it`);
|
|
211
216
|
}
|
|
@@ -248,11 +253,7 @@ export const harnessRetire = buildTool({
|
|
|
248
253
|
// symlink named like one of these would send the retirement log or the
|
|
249
254
|
// fingerprint manifest out of the workspace — the two files that are the
|
|
250
255
|
// whole evidence of the decommission.
|
|
251
|
-
const archiveEscapes = containWritePaths(tool, archive.value, [
|
|
252
|
-
STATE_MANIFEST_FILENAME,
|
|
253
|
-
RETIREMENT_LOG_FILENAME,
|
|
254
|
-
ARCHIVED_STATE_DIRNAME,
|
|
255
|
-
]);
|
|
256
|
+
const archiveEscapes = containWritePaths(tool, archive.value, [STATE_MANIFEST_FILENAME, RETIREMENT_LOG_FILENAME, ARCHIVED_STATE_DIRNAME], protectedRootsOf(ctx));
|
|
256
257
|
if (archiveEscapes !== undefined) {
|
|
257
258
|
return refusal(tool, archiveEscapes.code, archiveEscapes.reason);
|
|
258
259
|
}
|
|
@@ -529,20 +530,14 @@ export const storeMigrate = buildTool({
|
|
|
529
530
|
// the workspace — and a DANGLING one does not even register as a
|
|
530
531
|
// conflict. Checked on the preview too, so the refusal is visible before
|
|
531
532
|
// the run that would have written through it.
|
|
532
|
-
|
|
533
|
-
if (escaping !== undefined)
|
|
534
|
-
return refusal(tool, escaping.code, escaping.reason);
|
|
533
|
+
//
|
|
535
534
|
// 0.8.0 — no copy lands in the run's own record (`RuntimeBridge
|
|
536
535
|
// .protectedRoots`): a destination of `.crewhaus` would write
|
|
537
536
|
// `.crewhaus/sessions/<id>.jsonl` over the live session log. Refused in
|
|
538
537
|
// every mode, as StateImport and CopyPath refuse.
|
|
539
|
-
const
|
|
540
|
-
if (
|
|
541
|
-
|
|
542
|
-
if (into !== undefined) {
|
|
543
|
-
return protectedWriteRefusal(tool, `migrating onto ${input.to} (${into})`);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
538
|
+
const escaping = containWritePaths(tool, dest.value, written, protectedRootsOf(ctx));
|
|
539
|
+
if (escaping !== undefined)
|
|
540
|
+
return refusal(tool, escaping.code, escaping.reason);
|
|
546
541
|
const destState = inspectDestination(dest.value.real, written);
|
|
547
542
|
if (!destState.ok)
|
|
548
543
|
return refusal(tool, destState.code, destState.reason);
|
|
@@ -958,9 +953,10 @@ export const knowledgeSync = buildTool({
|
|
|
958
953
|
}),
|
|
959
954
|
destructive: true,
|
|
960
955
|
requireJustification: true,
|
|
961
|
-
execute: async (input) => {
|
|
956
|
+
execute: async (input, ctx) => {
|
|
962
957
|
const tool = "KnowledgeSync";
|
|
963
958
|
const dryRun = input.dryRun ?? true;
|
|
959
|
+
const protectedRoots = protectedRootsOf(ctx);
|
|
964
960
|
const maxArtifacts = input.maxArtifacts ?? DEFAULT_MAX_ARTIFACTS;
|
|
965
961
|
const harnessDir = containExistingDir(tool, input.dir ?? ".");
|
|
966
962
|
if (!harnessDir.ok)
|
|
@@ -1080,7 +1076,7 @@ export const knowledgeSync = buildTool({
|
|
|
1080
1076
|
SHARED_MANIFEST_RELPATH,
|
|
1081
1077
|
...plan.fragments.map(sharedFragmentRelPath),
|
|
1082
1078
|
];
|
|
1083
|
-
const pushEscapes = containWritePaths(tool, shared.value, pushWrites);
|
|
1079
|
+
const pushEscapes = containWritePaths(tool, shared.value, pushWrites, protectedRoots);
|
|
1084
1080
|
if (pushEscapes !== undefined) {
|
|
1085
1081
|
return refusal(tool, pushEscapes.code, pushEscapes.reason, { ...common, plan: planReport });
|
|
1086
1082
|
}
|
|
@@ -1180,7 +1176,7 @@ export const knowledgeSync = buildTool({
|
|
|
1180
1176
|
...(plan.memories.length > 0 ? [PULLED_MEMORIES_RELPATH] : []),
|
|
1181
1177
|
...plan.fragments.map(pulledFragmentRelPath),
|
|
1182
1178
|
];
|
|
1183
|
-
const pullEscapes = containWritePaths(tool, harnessDir.value, pullWrites);
|
|
1179
|
+
const pullEscapes = containWritePaths(tool, harnessDir.value, pullWrites, protectedRoots);
|
|
1184
1180
|
if (pullEscapes !== undefined) {
|
|
1185
1181
|
return refusal(tool, pullEscapes.code, pullEscapes.reason, { ...common, plan: planReport });
|
|
1186
1182
|
}
|
package/dist/lib/result.d.ts
CHANGED
|
@@ -62,8 +62,12 @@ export declare function contain(toolName: string, rel: string): Loaded<SafePath>
|
|
|
62
62
|
* through — symlink-aware, dangling links followed by hand — rather than a
|
|
63
63
|
* second symlink walker written here. Returns the refusal, or `undefined`
|
|
64
64
|
* when every write path stays inside the workspace.
|
|
65
|
+
*
|
|
66
|
+
* With `protectedRoots` (`RuntimeBridge.protectedRoots`, for a call that writes
|
|
67
|
+
* these paths), a path also has to land outside each of them. The landing place
|
|
68
|
+
* is the one `resolveSafe` worked out, which a dangling link's target counts in.
|
|
65
69
|
*/
|
|
66
|
-
export declare function containWritePaths(toolName: string, root: SafePath, rels: ReadonlyArray<string
|
|
70
|
+
export declare function containWritePaths(toolName: string, root: SafePath, rels: ReadonlyArray<string>, protectedRoots?: readonly string[]): Failure | undefined;
|
|
67
71
|
/** Resolve a directory that must already exist. */
|
|
68
72
|
export declare function containExistingDir(toolName: string, rel: string): Loaded<SafePath>;
|
|
69
73
|
/** True when `candidate` is `parent` or lives underneath it (both absolute). */
|
package/dist/lib/result.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { statSync } from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
|
+
import { protectedRootHolding, protectedWriteRefusal } from "@crewhaus/tool-safety/fs";
|
|
15
16
|
import { MAX_PATH_CHARS, ToolPermissionError, resolveSafe } from "../paths";
|
|
16
17
|
/** Compact JSON — the reader is a model, and every byte is context. */
|
|
17
18
|
export const json = (value) => JSON.stringify(value);
|
|
@@ -94,17 +95,25 @@ export function contain(toolName, rel) {
|
|
|
94
95
|
* through — symlink-aware, dangling links followed by hand — rather than a
|
|
95
96
|
* second symlink walker written here. Returns the refusal, or `undefined`
|
|
96
97
|
* when every write path stays inside the workspace.
|
|
98
|
+
*
|
|
99
|
+
* With `protectedRoots` (`RuntimeBridge.protectedRoots`, for a call that writes
|
|
100
|
+
* these paths), a path also has to land outside each of them. The landing place
|
|
101
|
+
* is the one `resolveSafe` worked out, which a dangling link's target counts in.
|
|
97
102
|
*/
|
|
98
|
-
export function containWritePaths(toolName, root, rels) {
|
|
103
|
+
export function containWritePaths(toolName, root, rels, protectedRoots = []) {
|
|
99
104
|
const shownRoot = root.rel === "" ? "." : root.rel;
|
|
100
105
|
for (const rel of rels) {
|
|
101
106
|
const under = root.rel === "" ? rel : `${root.rel}/${rel}`;
|
|
102
107
|
// Too long to open or create: it leads nowhere, so it is no escape.
|
|
103
108
|
if (pastPathLimit(under))
|
|
104
109
|
continue;
|
|
105
|
-
|
|
110
|
+
const resolved = contain(toolName, under);
|
|
111
|
+
if (!resolved.ok) {
|
|
106
112
|
return fail("refused", `"${renderPath(rel)}" under "${renderPath(shownRoot)}" resolves outside the workspace root — a symlink on that path leads out, so the write would land on the link's target rather than inside "${renderPath(shownRoot)}". Remove the link and run again; there is no flag for writing through it.`);
|
|
107
113
|
}
|
|
114
|
+
if (protectedRootHolding(resolved.value.real, protectedRoots) !== undefined) {
|
|
115
|
+
return fail("refused", protectedWriteRefusal(toolName, `"${renderPath(rel)}" under "${renderPath(shownRoot)}"`));
|
|
116
|
+
}
|
|
108
117
|
}
|
|
109
118
|
return undefined;
|
|
110
119
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/tool-lifecycle",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deterministic harness lifecycle: retirement, store migration, retention enforcement and knowledge sync",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"test": "bun test src"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@crewhaus/errors": "0.8.
|
|
20
|
-
"@crewhaus/harness-lifecycle": "0.8.
|
|
21
|
-
"@crewhaus/tool-builder": "0.8.
|
|
22
|
-
"@crewhaus/tool-catalog": "0.8.
|
|
23
|
-
"@crewhaus/tool-safety": "0.8.
|
|
19
|
+
"@crewhaus/errors": "0.8.2",
|
|
20
|
+
"@crewhaus/harness-lifecycle": "0.8.2",
|
|
21
|
+
"@crewhaus/tool-builder": "0.8.2",
|
|
22
|
+
"@crewhaus/tool-catalog": "0.8.2",
|
|
23
|
+
"@crewhaus/tool-safety": "0.8.2",
|
|
24
24
|
"zod": "^3.23.8"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|