clawclamp 0.1.10 → 0.1.11
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/package.json +1 -1
- package/src/policy-store.ts +106 -15
- package/src/policy.ts +22 -4
package/package.json
CHANGED
package/src/policy-store.ts
CHANGED
|
@@ -10,12 +10,28 @@ const POLICY_FILE = "policy-store.json";
|
|
|
10
10
|
|
|
11
11
|
export type PolicyEntry = { id: string; content: string };
|
|
12
12
|
|
|
13
|
+
type PolicyRecord = {
|
|
14
|
+
cedar_version?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
policy_content: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type PolicyStoreBody = {
|
|
21
|
+
name?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
schema?: string;
|
|
24
|
+
trusted_issuers?: Record<string, unknown>;
|
|
25
|
+
policies?: Record<string, PolicyRecord>;
|
|
26
|
+
};
|
|
27
|
+
|
|
13
28
|
export type PolicyStoreSnapshot = {
|
|
14
29
|
cedar_version: string;
|
|
15
|
-
|
|
16
|
-
policies: Record<string, { policy_content: string }>;
|
|
30
|
+
policy_stores: Record<string, PolicyStoreBody>;
|
|
17
31
|
};
|
|
18
32
|
|
|
33
|
+
const POLICY_STORE_ID = "clawclamp";
|
|
34
|
+
|
|
19
35
|
function resolvePolicyPath(stateDir: string): string {
|
|
20
36
|
return path.join(stateDir, "clawclamp", POLICY_FILE);
|
|
21
37
|
}
|
|
@@ -34,7 +50,7 @@ async function readPolicyStore(stateDir: string): Promise<PolicyStoreSnapshot> {
|
|
|
34
50
|
filePath,
|
|
35
51
|
buildDefaultPolicyStore() as PolicyStoreSnapshot,
|
|
36
52
|
);
|
|
37
|
-
return value;
|
|
53
|
+
return normalizePolicyStore(value);
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
async function writePolicyStore(stateDir: string, store: PolicyStoreSnapshot): Promise<void> {
|
|
@@ -43,6 +59,62 @@ async function writePolicyStore(stateDir: string, store: PolicyStoreSnapshot): P
|
|
|
43
59
|
await writeJsonFileAtomically(filePath, store);
|
|
44
60
|
}
|
|
45
61
|
|
|
62
|
+
function normalizePolicyStore(raw: unknown): PolicyStoreSnapshot {
|
|
63
|
+
const fallback = buildDefaultPolicyStore() as PolicyStoreSnapshot;
|
|
64
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const candidate = raw as Record<string, unknown>;
|
|
69
|
+
if (candidate.policy_stores && typeof candidate.policy_stores === "object") {
|
|
70
|
+
return candidate as PolicyStoreSnapshot;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const legacyPolicies =
|
|
74
|
+
candidate.policies && typeof candidate.policies === "object" && !Array.isArray(candidate.policies)
|
|
75
|
+
? (candidate.policies as Record<string, PolicyRecord>)
|
|
76
|
+
: {};
|
|
77
|
+
const legacySchema =
|
|
78
|
+
candidate.schema && typeof candidate.schema === "object" && !Array.isArray(candidate.schema)
|
|
79
|
+
? ((candidate.schema as Record<string, string>)["schema.json"] ?? "")
|
|
80
|
+
: "";
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
cedar_version:
|
|
84
|
+
typeof candidate.cedar_version === "string" ? candidate.cedar_version : fallback.cedar_version,
|
|
85
|
+
policy_stores: {
|
|
86
|
+
[POLICY_STORE_ID]: {
|
|
87
|
+
name: "Clawclamp Policy Store",
|
|
88
|
+
description: "Migrated legacy policy store.",
|
|
89
|
+
schema:
|
|
90
|
+
legacySchema ||
|
|
91
|
+
fallback.policy_stores[POLICY_STORE_ID]?.schema ||
|
|
92
|
+
"",
|
|
93
|
+
trusted_issuers: {},
|
|
94
|
+
policies: legacyPolicies,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getWritableStore(store: PolicyStoreSnapshot): PolicyStoreBody {
|
|
101
|
+
const existing = store.policy_stores?.[POLICY_STORE_ID];
|
|
102
|
+
if (existing) {
|
|
103
|
+
return existing;
|
|
104
|
+
}
|
|
105
|
+
const fallback = buildDefaultPolicyStore() as PolicyStoreSnapshot;
|
|
106
|
+
const created = fallback.policy_stores[POLICY_STORE_ID] ?? {
|
|
107
|
+
policies: {},
|
|
108
|
+
schema: "",
|
|
109
|
+
trusted_issuers: {},
|
|
110
|
+
};
|
|
111
|
+
if (!store.policy_stores) {
|
|
112
|
+
store.policy_stores = {};
|
|
113
|
+
}
|
|
114
|
+
store.policy_stores[POLICY_STORE_ID] = created;
|
|
115
|
+
return created;
|
|
116
|
+
}
|
|
117
|
+
|
|
46
118
|
export async function ensurePolicyStore(params: {
|
|
47
119
|
stateDir: string;
|
|
48
120
|
config: ClawClampConfig;
|
|
@@ -54,7 +126,12 @@ export async function ensurePolicyStore(params: {
|
|
|
54
126
|
const filePath = resolvePolicyPath(params.stateDir);
|
|
55
127
|
try {
|
|
56
128
|
const raw = await fs.readFile(filePath, "utf8");
|
|
57
|
-
|
|
129
|
+
const normalized = normalizePolicyStore(JSON.parse(raw));
|
|
130
|
+
const json = JSON.stringify(normalized);
|
|
131
|
+
if (json !== raw) {
|
|
132
|
+
await writePolicyStore(params.stateDir, normalized);
|
|
133
|
+
}
|
|
134
|
+
return { json, readOnly: false };
|
|
58
135
|
} catch (error) {
|
|
59
136
|
const code = (error as { code?: string }).code;
|
|
60
137
|
if (code !== "ENOENT") {
|
|
@@ -63,7 +140,7 @@ export async function ensurePolicyStore(params: {
|
|
|
63
140
|
}
|
|
64
141
|
|
|
65
142
|
const initial = params.config.policyStoreLocal
|
|
66
|
-
? (JSON.parse(params.config.policyStoreLocal)
|
|
143
|
+
? normalizePolicyStore(JSON.parse(params.config.policyStoreLocal))
|
|
67
144
|
: (buildDefaultPolicyStore() as PolicyStoreSnapshot);
|
|
68
145
|
await writePolicyStore(params.stateDir, initial);
|
|
69
146
|
return { json: JSON.stringify(initial), readOnly: false };
|
|
@@ -75,11 +152,12 @@ export async function listPolicies(params: {
|
|
|
75
152
|
}): Promise<{ policies: PolicyEntry[]; schema: string }> {
|
|
76
153
|
return withStateFileLock(params.stateDir, "policy-store", async () => {
|
|
77
154
|
const store = await readPolicyStore(params.stateDir);
|
|
78
|
-
const
|
|
155
|
+
const policyStore = getWritableStore(store);
|
|
156
|
+
const policies: PolicyEntry[] = Object.entries(policyStore.policies ?? {}).map(([id, payload]) => ({
|
|
79
157
|
id,
|
|
80
158
|
content: decodeBase64(payload.policy_content ?? ""),
|
|
81
159
|
}));
|
|
82
|
-
const schemaEncoded =
|
|
160
|
+
const schemaEncoded = policyStore.schema ?? "";
|
|
83
161
|
return { policies, schema: schemaEncoded ? decodeBase64(schemaEncoded) : "" };
|
|
84
162
|
});
|
|
85
163
|
}
|
|
@@ -91,14 +169,20 @@ export async function createPolicy(params: {
|
|
|
91
169
|
}): Promise<PolicyEntry> {
|
|
92
170
|
return withStateFileLock(params.stateDir, "policy-store", async () => {
|
|
93
171
|
const store = await readPolicyStore(params.stateDir);
|
|
172
|
+
const policyStore = getWritableStore(store);
|
|
94
173
|
const id = params.id?.trim() || `clawclamp-${randomUUID()}`;
|
|
95
|
-
if (!
|
|
96
|
-
|
|
174
|
+
if (!policyStore.policies) {
|
|
175
|
+
policyStore.policies = {};
|
|
97
176
|
}
|
|
98
|
-
if (
|
|
177
|
+
if (policyStore.policies[id]) {
|
|
99
178
|
throw new Error("policy id already exists");
|
|
100
179
|
}
|
|
101
|
-
|
|
180
|
+
policyStore.policies[id] = {
|
|
181
|
+
cedar_version: store.cedar_version,
|
|
182
|
+
name: id,
|
|
183
|
+
description: "Created from Clawclamp UI.",
|
|
184
|
+
policy_content: encodeBase64(params.content),
|
|
185
|
+
};
|
|
102
186
|
await writePolicyStore(params.stateDir, store);
|
|
103
187
|
return { id, content: params.content };
|
|
104
188
|
});
|
|
@@ -111,10 +195,16 @@ export async function updatePolicy(params: {
|
|
|
111
195
|
}): Promise<PolicyEntry> {
|
|
112
196
|
return withStateFileLock(params.stateDir, "policy-store", async () => {
|
|
113
197
|
const store = await readPolicyStore(params.stateDir);
|
|
114
|
-
|
|
198
|
+
const policyStore = getWritableStore(store);
|
|
199
|
+
if (!policyStore.policies?.[params.id]) {
|
|
115
200
|
throw new Error("policy id not found");
|
|
116
201
|
}
|
|
117
|
-
|
|
202
|
+
policyStore.policies[params.id] = {
|
|
203
|
+
cedar_version: store.cedar_version,
|
|
204
|
+
name: params.id,
|
|
205
|
+
description: "Updated from Clawclamp UI.",
|
|
206
|
+
policy_content: encodeBase64(params.content),
|
|
207
|
+
};
|
|
118
208
|
await writePolicyStore(params.stateDir, store);
|
|
119
209
|
return { id: params.id, content: params.content };
|
|
120
210
|
});
|
|
@@ -123,10 +213,11 @@ export async function updatePolicy(params: {
|
|
|
123
213
|
export async function deletePolicy(params: { stateDir: string; id: string }): Promise<boolean> {
|
|
124
214
|
return withStateFileLock(params.stateDir, "policy-store", async () => {
|
|
125
215
|
const store = await readPolicyStore(params.stateDir);
|
|
126
|
-
|
|
216
|
+
const policyStore = getWritableStore(store);
|
|
217
|
+
if (!policyStore.policies?.[params.id]) {
|
|
127
218
|
return false;
|
|
128
219
|
}
|
|
129
|
-
delete
|
|
220
|
+
delete policyStore.policies[params.id];
|
|
130
221
|
await writePolicyStore(params.stateDir, store);
|
|
131
222
|
return true;
|
|
132
223
|
});
|
package/src/policy.ts
CHANGED
|
@@ -31,23 +31,41 @@ when {
|
|
|
31
31
|
};`,
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
+
const POLICY_STORE_ID = "clawclamp";
|
|
35
|
+
|
|
34
36
|
function toBase64(raw: string): string {
|
|
35
37
|
return Buffer.from(raw, "utf8").toString("base64");
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function buildDefaultPolicyStore(): Record<string, unknown> {
|
|
39
|
-
const policies: Record<
|
|
41
|
+
const policies: Record<
|
|
42
|
+
string,
|
|
43
|
+
{
|
|
44
|
+
cedar_version: string;
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
policy_content: string;
|
|
48
|
+
}
|
|
49
|
+
> = {};
|
|
40
50
|
DEFAULT_POLICIES.forEach((policy, index) => {
|
|
41
51
|
policies[`openclaw-clawclamp-${index + 1}`] = {
|
|
52
|
+
cedar_version: "v4.0.0",
|
|
53
|
+
name: `Clawclamp Default Policy ${index + 1}`,
|
|
54
|
+
description: "Default grant-based permit policy.",
|
|
42
55
|
policy_content: toBase64(policy),
|
|
43
56
|
};
|
|
44
57
|
});
|
|
45
58
|
|
|
46
59
|
return {
|
|
47
60
|
cedar_version: "v4.0.0",
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
policy_stores: {
|
|
62
|
+
[POLICY_STORE_ID]: {
|
|
63
|
+
name: "Clawclamp Policy Store",
|
|
64
|
+
description: "Local Cedar policies for Clawclamp.",
|
|
65
|
+
policies,
|
|
66
|
+
schema: toBase64(DEFAULT_SCHEMA),
|
|
67
|
+
trusted_issuers: {},
|
|
68
|
+
},
|
|
50
69
|
},
|
|
51
|
-
policies,
|
|
52
70
|
};
|
|
53
71
|
}
|