@clef-sh/core 0.1.7-beta.43 → 0.1.7-beta.45

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/dist/index.mjs CHANGED
@@ -1012,6 +1012,7 @@ var ScanRunner = class {
1012
1012
  // src/matrix/manager.ts
1013
1013
  import * as fs5 from "fs";
1014
1014
  import * as path4 from "path";
1015
+ import * as YAML3 from "yaml";
1015
1016
 
1016
1017
  // src/pending/metadata.ts
1017
1018
  import * as fs4 from "fs";
@@ -1156,9 +1157,15 @@ var MatrixManager = class {
1156
1157
  * @param repoRoot - Absolute path to the repository root.
1157
1158
  * @param sopsClient - SOPS client used to decrypt each cell.
1158
1159
  */
1159
- async getMatrixStatus(manifest, repoRoot, sopsClient) {
1160
+ async getMatrixStatus(manifest, repoRoot, _sopsClient) {
1160
1161
  const cells = this.resolveMatrix(manifest, repoRoot);
1161
1162
  const statuses = [];
1163
+ const cellKeys = /* @__PURE__ */ new Map();
1164
+ for (const cell of cells) {
1165
+ if (cell.exists) {
1166
+ cellKeys.set(cell.filePath, this.readKeyNames(cell.filePath));
1167
+ }
1168
+ }
1162
1169
  for (const cell of cells) {
1163
1170
  if (!cell.exists) {
1164
1171
  statuses.push({
@@ -1181,48 +1188,56 @@ var MatrixManager = class {
1181
1188
  pendingCount = pending.length;
1182
1189
  } catch {
1183
1190
  }
1184
- try {
1185
- const decrypted = await sopsClient.decrypt(cell.filePath);
1186
- const keyCount = Object.keys(decrypted.values).length;
1187
- const lastModified = decrypted.metadata.lastModified;
1188
- const issues = [];
1189
- const siblingCells = cells.filter(
1190
- (c) => c.namespace === cell.namespace && c.environment !== cell.environment && c.exists
1191
- );
1192
- for (const sibling of siblingCells) {
1193
- try {
1194
- const siblingDecrypted = await sopsClient.decrypt(sibling.filePath);
1195
- const siblingKeys = Object.keys(siblingDecrypted.values);
1196
- const currentKeys = Object.keys(decrypted.values);
1197
- const missingKeys = siblingKeys.filter((k) => !currentKeys.includes(k));
1198
- for (const mk of missingKeys) {
1199
- issues.push({
1200
- type: "missing_keys",
1201
- message: `Key '${mk}' exists in ${sibling.environment} but is missing here.`,
1202
- key: mk
1203
- });
1204
- }
1205
- } catch {
1206
- }
1191
+ const keys = cellKeys.get(cell.filePath) ?? [];
1192
+ const keyCount = keys.length;
1193
+ const lastModified = this.readLastModified(cell.filePath);
1194
+ const issues = [];
1195
+ const siblingCells = cells.filter(
1196
+ (c) => c.namespace === cell.namespace && c.environment !== cell.environment && c.exists
1197
+ );
1198
+ for (const sibling of siblingCells) {
1199
+ const siblingKeys = cellKeys.get(sibling.filePath) ?? [];
1200
+ const missingKeys = siblingKeys.filter((k) => !keys.includes(k));
1201
+ for (const mk of missingKeys) {
1202
+ issues.push({
1203
+ type: "missing_keys",
1204
+ message: `Key '${mk}' exists in ${sibling.environment} but is missing here.`,
1205
+ key: mk
1206
+ });
1207
1207
  }
1208
- statuses.push({ cell, keyCount, pendingCount, lastModified, issues });
1209
- } catch {
1210
- statuses.push({
1211
- cell,
1212
- keyCount: 0,
1213
- pendingCount: 0,
1214
- lastModified: null,
1215
- issues: [
1216
- {
1217
- type: "sops_error",
1218
- message: `Could not decrypt '${cell.filePath}'. Check your key configuration.`
1219
- }
1220
- ]
1221
- });
1222
1208
  }
1209
+ statuses.push({ cell, keyCount, pendingCount, lastModified, issues });
1223
1210
  }
1224
1211
  return statuses;
1225
1212
  }
1213
+ /**
1214
+ * Read top-level key names from a SOPS file without decryption.
1215
+ * SOPS stores key names in plaintext — only values are encrypted.
1216
+ */
1217
+ readKeyNames(filePath) {
1218
+ try {
1219
+ const raw = fs5.readFileSync(filePath, "utf-8");
1220
+ const parsed = YAML3.parse(raw);
1221
+ if (!parsed || typeof parsed !== "object") return [];
1222
+ return Object.keys(parsed).filter((k) => k !== "sops");
1223
+ } catch {
1224
+ return [];
1225
+ }
1226
+ }
1227
+ /**
1228
+ * Read the lastModified timestamp from SOPS metadata without decryption.
1229
+ */
1230
+ readLastModified(filePath) {
1231
+ try {
1232
+ const raw = fs5.readFileSync(filePath, "utf-8");
1233
+ const parsed = YAML3.parse(raw);
1234
+ const sops = parsed?.sops;
1235
+ if (sops?.lastmodified) return new Date(String(sops.lastmodified));
1236
+ return null;
1237
+ } catch {
1238
+ return null;
1239
+ }
1240
+ }
1226
1241
  /**
1227
1242
  * Check whether an environment has the `protected` flag set in the manifest.
1228
1243
  *
@@ -1237,7 +1252,7 @@ var MatrixManager = class {
1237
1252
 
1238
1253
  // src/schema/validator.ts
1239
1254
  import * as fs6 from "fs";
1240
- import * as YAML3 from "yaml";
1255
+ import * as YAML4 from "yaml";
1241
1256
  var SchemaValidator = class {
1242
1257
  /**
1243
1258
  * Read and parse a YAML schema file from disk.
@@ -1255,7 +1270,7 @@ var SchemaValidator = class {
1255
1270
  }
1256
1271
  let parsed;
1257
1272
  try {
1258
- parsed = YAML3.parse(raw);
1273
+ parsed = YAML4.parse(raw);
1259
1274
  } catch {
1260
1275
  throw new SchemaLoadError(`Schema file '${filePath}' contains invalid YAML.`, filePath);
1261
1276
  }
@@ -1823,7 +1838,7 @@ ${mergeRule}
1823
1838
  import * as fs10 from "fs";
1824
1839
  import * as net from "net";
1825
1840
  import { randomBytes as randomBytes2 } from "crypto";
1826
- import * as YAML4 from "yaml";
1841
+ import * as YAML5 from "yaml";
1827
1842
 
1828
1843
  // src/sops/resolver.ts
1829
1844
  import * as fs9 from "fs";
@@ -2077,7 +2092,7 @@ var SopsClient = class {
2077
2092
  }
2078
2093
  let parsed;
2079
2094
  try {
2080
- parsed = YAML4.parse(result.stdout) ?? {};
2095
+ parsed = YAML5.parse(result.stdout) ?? {};
2081
2096
  } catch {
2082
2097
  throw new SopsDecryptionError(
2083
2098
  `Decrypted content of '${filePath}' is not valid YAML.`,
@@ -2104,7 +2119,7 @@ var SopsClient = class {
2104
2119
  async encrypt(filePath, values, manifest, environment) {
2105
2120
  await assertSops(this.runner, this.sopsCommand);
2106
2121
  const fmt = formatFromPath(filePath);
2107
- const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML4.stringify(values);
2122
+ const content = fmt === "json" ? JSON.stringify(values, null, 2) : YAML5.stringify(values);
2108
2123
  const args = this.buildEncryptArgs(filePath, manifest, environment);
2109
2124
  const env = this.buildSopsEnv();
2110
2125
  let inputArg;
@@ -2304,7 +2319,7 @@ var SopsClient = class {
2304
2319
  }
2305
2320
  let parsed;
2306
2321
  try {
2307
- parsed = YAML4.parse(content);
2322
+ parsed = YAML5.parse(content);
2308
2323
  } catch {
2309
2324
  throw new SopsDecryptionError(
2310
2325
  `File '${filePath}' is not valid YAML. Cannot extract SOPS metadata.`,
@@ -2736,7 +2751,7 @@ import * as path12 from "path";
2736
2751
 
2737
2752
  // src/import/parsers.ts
2738
2753
  import * as path11 from "path";
2739
- import * as YAML5 from "yaml";
2754
+ import * as YAML6 from "yaml";
2740
2755
  function detectFormat(filePath, content) {
2741
2756
  const base = path11.basename(filePath);
2742
2757
  const ext = path11.extname(filePath).toLowerCase();
@@ -2760,7 +2775,7 @@ function detectFormat(filePath, content) {
2760
2775
  } catch {
2761
2776
  }
2762
2777
  try {
2763
- const parsed = YAML5.parse(content);
2778
+ const parsed = YAML6.parse(content);
2764
2779
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
2765
2780
  return "yaml";
2766
2781
  }
@@ -2841,7 +2856,7 @@ function parseJson(content) {
2841
2856
  function parseYaml(content) {
2842
2857
  let parsed;
2843
2858
  try {
2844
- parsed = YAML5.parse(content);
2859
+ parsed = YAML6.parse(content);
2845
2860
  } catch (err) {
2846
2861
  throw new Error(`Invalid YAML: ${err.message}`);
2847
2862
  }
@@ -2875,7 +2890,7 @@ function parseYaml(content) {
2875
2890
  }
2876
2891
  return { pairs, format: "yaml", skipped, warnings };
2877
2892
  }
2878
- function parse6(content, format, filePath) {
2893
+ function parse7(content, format, filePath) {
2879
2894
  const resolved = format === "auto" ? detectFormat(filePath ?? "", content) : format;
2880
2895
  switch (resolved) {
2881
2896
  case "dotenv":
@@ -2908,7 +2923,7 @@ var ImportRunner = class {
2908
2923
  repoRoot,
2909
2924
  manifest.file_pattern.replace("{namespace}", ns).replace("{environment}", env)
2910
2925
  );
2911
- const parsed = parse6(content, options.format ?? "auto", sourcePath ?? "");
2926
+ const parsed = parse7(content, options.format ?? "auto", sourcePath ?? "");
2912
2927
  let candidates = Object.entries(parsed.pairs);
2913
2928
  if (options.prefix) {
2914
2929
  const prefix = options.prefix;
@@ -2964,7 +2979,7 @@ var ImportRunner = class {
2964
2979
  // src/recipients/index.ts
2965
2980
  import * as fs11 from "fs";
2966
2981
  import * as path13 from "path";
2967
- import * as YAML6 from "yaml";
2982
+ import * as YAML7 from "yaml";
2968
2983
  function parseRecipientEntry(entry) {
2969
2984
  if (typeof entry === "string") {
2970
2985
  return { key: entry };
@@ -2988,11 +3003,11 @@ function toRecipient(entry) {
2988
3003
  function readManifestYaml(repoRoot) {
2989
3004
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
2990
3005
  const raw = fs11.readFileSync(manifestPath, "utf-8");
2991
- return YAML6.parse(raw);
3006
+ return YAML7.parse(raw);
2992
3007
  }
2993
3008
  function writeManifestYaml(repoRoot, doc) {
2994
3009
  const manifestPath = path13.join(repoRoot, CLEF_MANIFEST_FILENAME);
2995
- fs11.writeFileSync(manifestPath, YAML6.stringify(doc), "utf-8");
3010
+ fs11.writeFileSync(manifestPath, YAML7.stringify(doc), "utf-8");
2996
3011
  }
2997
3012
  function getRecipientsArray(doc) {
2998
3013
  const sops = doc.sops;
@@ -3227,7 +3242,7 @@ var RecipientManager = class {
3227
3242
  // src/recipients/requests.ts
3228
3243
  import * as fs12 from "fs";
3229
3244
  import * as path14 from "path";
3230
- import * as YAML7 from "yaml";
3245
+ import * as YAML8 from "yaml";
3231
3246
  var REQUESTS_FILENAME = ".clef-requests.yaml";
3232
3247
  var HEADER_COMMENT2 = "# Pending recipient access requests. Approve with: clef recipients approve <label>\n";
3233
3248
  function requestsFilePath(repoRoot) {
@@ -3238,7 +3253,7 @@ function loadRequests(repoRoot) {
3238
3253
  try {
3239
3254
  if (!fs12.existsSync(filePath)) return [];
3240
3255
  const content = fs12.readFileSync(filePath, "utf-8");
3241
- const parsed = YAML7.parse(content);
3256
+ const parsed = YAML8.parse(content);
3242
3257
  if (!parsed || !Array.isArray(parsed.requests)) return [];
3243
3258
  return parsed.requests.map((r) => ({
3244
3259
  key: r.key,
@@ -3270,7 +3285,7 @@ function saveRequests(repoRoot, requests) {
3270
3285
  return raw;
3271
3286
  })
3272
3287
  };
3273
- fs12.writeFileSync(filePath, HEADER_COMMENT2 + YAML7.stringify(data), "utf-8");
3288
+ fs12.writeFileSync(filePath, HEADER_COMMENT2 + YAML8.stringify(data), "utf-8");
3274
3289
  }
3275
3290
  function upsertRequest(repoRoot, key, label, environment) {
3276
3291
  const requests = loadRequests(repoRoot);
@@ -3308,7 +3323,7 @@ function findInList(requests, identifier) {
3308
3323
  // src/drift/detector.ts
3309
3324
  import * as fs13 from "fs";
3310
3325
  import * as path15 from "path";
3311
- import * as YAML8 from "yaml";
3326
+ import * as YAML9 from "yaml";
3312
3327
  var DriftDetector = class {
3313
3328
  parser = new ManifestParser();
3314
3329
  matrix = new MatrixManager();
@@ -3395,7 +3410,7 @@ var DriftDetector = class {
3395
3410
  try {
3396
3411
  if (!fs13.existsSync(filePath)) return null;
3397
3412
  const raw = fs13.readFileSync(filePath, "utf-8");
3398
- const parsed = YAML8.parse(raw);
3413
+ const parsed = YAML9.parse(raw);
3399
3414
  if (parsed === null || parsed === void 0 || typeof parsed !== "object") return null;
3400
3415
  return Object.keys(parsed).filter((k) => k !== "sops");
3401
3416
  } catch {
@@ -3407,7 +3422,7 @@ var DriftDetector = class {
3407
3422
  // src/report/generator.ts
3408
3423
  import * as fs14 from "fs";
3409
3424
  import * as path16 from "path";
3410
- import * as YAML9 from "yaml";
3425
+ import * as YAML10 from "yaml";
3411
3426
 
3412
3427
  // src/report/sanitizer.ts
3413
3428
  var ReportSanitizer = class {
@@ -3715,7 +3730,7 @@ var ReportGenerator = class {
3715
3730
  try {
3716
3731
  if (!fs14.existsSync(filePath)) return 0;
3717
3732
  const raw = fs14.readFileSync(filePath, "utf-8");
3718
- const parsed = YAML9.parse(raw);
3733
+ const parsed = YAML10.parse(raw);
3719
3734
  if (parsed === null || parsed === void 0 || typeof parsed !== "object") return 0;
3720
3735
  return Object.keys(parsed).filter((k) => k !== "sops").length;
3721
3736
  } catch {
@@ -4052,7 +4067,7 @@ var SopsMergeDriver = class {
4052
4067
  import * as fs15 from "fs";
4053
4068
  import * as os from "os";
4054
4069
  import * as path17 from "path";
4055
- import * as YAML10 from "yaml";
4070
+ import * as YAML11 from "yaml";
4056
4071
  var PartialRotationError = class extends Error {
4057
4072
  constructor(message, rotatedKeys) {
4058
4073
  super(message);
@@ -4105,7 +4120,7 @@ var ServiceIdentityManager = class {
4105
4120
  await this.registerRecipients(definition, manifest, repoRoot);
4106
4121
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4107
4122
  const raw = fs15.readFileSync(manifestPath, "utf-8");
4108
- const doc = YAML10.parse(raw);
4123
+ const doc = YAML11.parse(raw);
4109
4124
  if (!Array.isArray(doc.service_identities)) {
4110
4125
  doc.service_identities = [];
4111
4126
  }
@@ -4117,7 +4132,7 @@ var ServiceIdentityManager = class {
4117
4132
  });
4118
4133
  const tmpCreate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4119
4134
  try {
4120
- fs15.writeFileSync(tmpCreate, YAML10.stringify(doc), "utf-8");
4135
+ fs15.writeFileSync(tmpCreate, YAML11.stringify(doc), "utf-8");
4121
4136
  fs15.renameSync(tmpCreate, manifestPath);
4122
4137
  } finally {
4123
4138
  try {
@@ -4139,6 +4154,46 @@ var ServiceIdentityManager = class {
4139
4154
  get(manifest, name) {
4140
4155
  return manifest.service_identities?.find((si) => si.name === name);
4141
4156
  }
4157
+ /**
4158
+ * Delete a service identity: remove its recipients from scoped SOPS files
4159
+ * and remove it from the manifest.
4160
+ */
4161
+ async delete(name, manifest, repoRoot) {
4162
+ const identity = this.get(manifest, name);
4163
+ if (!identity) {
4164
+ throw new Error(`Service identity '${name}' not found.`);
4165
+ }
4166
+ const cells = this.matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.exists);
4167
+ for (const cell of cells) {
4168
+ if (!identity.namespaces.includes(cell.namespace)) continue;
4169
+ const envConfig = identity.environments[cell.environment];
4170
+ if (!envConfig?.recipient) continue;
4171
+ if (isKmsEnvelope(envConfig)) continue;
4172
+ try {
4173
+ await this.encryption.removeRecipient(cell.filePath, envConfig.recipient);
4174
+ } catch {
4175
+ }
4176
+ }
4177
+ const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4178
+ const raw = fs15.readFileSync(manifestPath, "utf-8");
4179
+ const doc = YAML11.parse(raw);
4180
+ const identities = doc.service_identities;
4181
+ if (Array.isArray(identities)) {
4182
+ doc.service_identities = identities.filter(
4183
+ (si) => si.name !== name
4184
+ );
4185
+ }
4186
+ const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4187
+ try {
4188
+ fs15.writeFileSync(tmp, YAML11.stringify(doc), "utf-8");
4189
+ fs15.renameSync(tmp, manifestPath);
4190
+ } finally {
4191
+ try {
4192
+ fs15.unlinkSync(tmp);
4193
+ } catch {
4194
+ }
4195
+ }
4196
+ }
4142
4197
  /**
4143
4198
  * Update environment backends on an existing service identity.
4144
4199
  * Switches age → KMS (removes old recipient) or updates KMS config.
@@ -4151,7 +4206,7 @@ var ServiceIdentityManager = class {
4151
4206
  }
4152
4207
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4153
4208
  const raw = fs15.readFileSync(manifestPath, "utf-8");
4154
- const doc = YAML10.parse(raw);
4209
+ const doc = YAML11.parse(raw);
4155
4210
  const identities = doc.service_identities;
4156
4211
  const siDoc = identities.find((si) => si.name === name);
4157
4212
  const envs = siDoc.environments;
@@ -4178,7 +4233,7 @@ var ServiceIdentityManager = class {
4178
4233
  }
4179
4234
  const tmp = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4180
4235
  try {
4181
- fs15.writeFileSync(tmp, YAML10.stringify(doc), "utf-8");
4236
+ fs15.writeFileSync(tmp, YAML11.stringify(doc), "utf-8");
4182
4237
  fs15.renameSync(tmp, manifestPath);
4183
4238
  } finally {
4184
4239
  try {
@@ -4220,7 +4275,7 @@ var ServiceIdentityManager = class {
4220
4275
  }
4221
4276
  const manifestPath = path17.join(repoRoot, CLEF_MANIFEST_FILENAME);
4222
4277
  const raw = fs15.readFileSync(manifestPath, "utf-8");
4223
- const doc = YAML10.parse(raw);
4278
+ const doc = YAML11.parse(raw);
4224
4279
  const identities = doc.service_identities;
4225
4280
  const siDoc = identities.find((si) => si.name === name);
4226
4281
  const envs = siDoc.environments;
@@ -4275,7 +4330,7 @@ var ServiceIdentityManager = class {
4275
4330
  }
4276
4331
  const tmpRotate = path17.join(os.tmpdir(), `clef-manifest-${process.pid}-${Date.now()}.tmp`);
4277
4332
  try {
4278
- fs15.writeFileSync(tmpRotate, YAML10.stringify(doc), "utf-8");
4333
+ fs15.writeFileSync(tmpRotate, YAML11.stringify(doc), "utf-8");
4279
4334
  fs15.renameSync(tmpRotate, manifestPath);
4280
4335
  } finally {
4281
4336
  try {
@@ -4439,7 +4494,7 @@ var ArtifactPacker = class {
4439
4494
  const e = new Encrypter();
4440
4495
  e.addRecipient(ephemeralPublicKey);
4441
4496
  const encrypted = await e.encrypt(plaintext);
4442
- ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
4497
+ ciphertext = Buffer.from(encrypted).toString("base64");
4443
4498
  } catch {
4444
4499
  throw new Error("Failed to age-encrypt artifact with ephemeral key.");
4445
4500
  }
@@ -4469,7 +4524,7 @@ var ArtifactPacker = class {
4469
4524
  const e = new Encrypter();
4470
4525
  e.addRecipient(resolved.recipient);
4471
4526
  const encrypted = await e.encrypt(plaintext);
4472
- ciphertext = typeof encrypted === "string" ? encrypted : Buffer.from(encrypted).toString("base64");
4527
+ ciphertext = Buffer.from(encrypted).toString("base64");
4473
4528
  } catch {
4474
4529
  throw new Error("Failed to age-encrypt artifact. Check recipient key.");
4475
4530
  }
@@ -4566,7 +4621,7 @@ export {
4566
4621
  markResolved,
4567
4622
  matchPatterns,
4568
4623
  metadataPath,
4569
- parse6 as parse,
4624
+ parse7 as parse,
4570
4625
  parseDotenv,
4571
4626
  parseIgnoreContent,
4572
4627
  parseJson,