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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawclamp",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "OpenClaw Cedar authorization guard with audit UI",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -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
- schema: Record<string, string>;
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
- return { json: raw, readOnly: false };
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) as PolicyStoreSnapshot)
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 policies: PolicyEntry[] = Object.entries(store.policies ?? {}).map(([id, payload]) => ({
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 = store.schema?.["schema.json"] ?? "";
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 (!store.policies) {
96
- store.policies = {};
174
+ if (!policyStore.policies) {
175
+ policyStore.policies = {};
97
176
  }
98
- if (store.policies[id]) {
177
+ if (policyStore.policies[id]) {
99
178
  throw new Error("policy id already exists");
100
179
  }
101
- store.policies[id] = { policy_content: encodeBase64(params.content) };
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
- if (!store.policies?.[params.id]) {
198
+ const policyStore = getWritableStore(store);
199
+ if (!policyStore.policies?.[params.id]) {
115
200
  throw new Error("policy id not found");
116
201
  }
117
- store.policies[params.id] = { policy_content: encodeBase64(params.content) };
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
- if (!store.policies?.[params.id]) {
216
+ const policyStore = getWritableStore(store);
217
+ if (!policyStore.policies?.[params.id]) {
127
218
  return false;
128
219
  }
129
- delete store.policies[params.id];
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<string, { policy_content: string }> = {};
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
- schema: {
49
- "schema.json": toBase64(DEFAULT_SCHEMA),
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
  }