@agent-finops/core 0.9.2 → 0.9.3

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.
@@ -1,6 +1,6 @@
1
1
  import { constants } from "node:fs";
2
2
  import { execFile as execFileCallback } from "node:child_process";
3
- import { open, lstat, mkdir, chmod, realpath, rename, unlink } from "node:fs/promises";
3
+ import { open, lstat, mkdir, chmod, readdir, realpath, rename, unlink } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, join, relative, resolve } from "node:path";
6
6
  import { randomUUID } from "node:crypto";
@@ -343,12 +343,48 @@ async function ensureDefaultParent(homeDirectory, create) {
343
343
  throw new ActivitySnapshotCacheError("unsafe_directory", "The private aibill directory is not a real directory.");
344
344
  }
345
345
  if (!create && !hasPrivatePermissions(info.mode)) {
346
- throw new ActivitySnapshotCacheError("unsafe_directory", "The private aibill directory is not private.");
346
+ // Self-heal (NEW-B1): 0.9.2's signup/telemetry state writers created
347
+ // ~/.aibill without a mode (755 under the default umask), which this
348
+ // guard then refused forever — bricking init on machines that ran
349
+ // 0.9.2 in that window. When the directory holds ONLY our own state
350
+ // files (no cache/ yet, nothing foreign), tighten it to 0700 and
351
+ // proceed instead of erroring. Anything unexpected still refuses.
352
+ if (!await selfHealStateOnlyAibillDirectory(parent)) {
353
+ throw new ActivitySnapshotCacheError("unsafe_directory", `The private aibill directory is not private. Fix: chmod 700 ${parent}`);
354
+ }
347
355
  }
348
356
  if (create)
349
357
  await chmod(parent, 0o700);
350
358
  await ensureDefaultCacheGitPrivacy(parent, create);
351
359
  }
360
+ /**
361
+ * The complete set of files aibill's own home-scope state writers may have
362
+ * placed in ~/.aibill before any cache exists. A directory with exactly
363
+ * these contents (or fewer) is provably ours to repair.
364
+ */
365
+ const selfHealableAibillEntries = new Set([
366
+ "signup.json",
367
+ "signup.json.tmp",
368
+ "telemetry.json",
369
+ "telemetry.json.tmp",
370
+ ".gitignore"
371
+ ]);
372
+ async function selfHealStateOnlyAibillDirectory(parent) {
373
+ try {
374
+ const entries = await readdir(parent, { withFileTypes: true });
375
+ for (const entry of entries) {
376
+ if (!entry.isFile() || !selfHealableAibillEntries.has(entry.name))
377
+ return false;
378
+ }
379
+ await chmod(parent, 0o700);
380
+ const confirmed = await lstat(parent);
381
+ return !confirmed.isSymbolicLink() && confirmed.isDirectory() &&
382
+ hasPrivatePermissions(confirmed.mode);
383
+ }
384
+ catch {
385
+ return false;
386
+ }
387
+ }
352
388
  /**
353
389
  * When a synthetic or real HOME is itself inside a Git worktree, protect the
354
390
  * complete top-level private state directory before any cache child is
@@ -0,0 +1,7 @@
1
+ /**
2
+ * One intentionally pragmatic normalizer for waitlist and receipt-email
3
+ * boundaries. It prevents header/control injection without pretending to be
4
+ * a mailbox-verification system; ownership still requires an explicit flow.
5
+ */
6
+ export declare function normalizeAibillEmailAddress(value: string): string | undefined;
7
+ //# sourceMappingURL=emailAddress.d.ts.map
@@ -0,0 +1,16 @@
1
+ const controlCharacters = /[\u0000-\u001F\u007F]/u;
2
+ const pragmaticEmailAddress = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
3
+ /**
4
+ * One intentionally pragmatic normalizer for waitlist and receipt-email
5
+ * boundaries. It prevents header/control injection without pretending to be
6
+ * a mailbox-verification system; ownership still requires an explicit flow.
7
+ */
8
+ export function normalizeAibillEmailAddress(value) {
9
+ const normalized = value.trim().toLowerCase();
10
+ if (normalized.length < 3 || normalized.length > 254)
11
+ return undefined;
12
+ if (controlCharacters.test(normalized))
13
+ return undefined;
14
+ return pragmaticEmailAddress.test(normalized) ? normalized : undefined;
15
+ }
16
+ //# sourceMappingURL=emailAddress.js.map
@@ -27,9 +27,9 @@ export declare const receiptShareCardV0Schema: z.ZodObject<{
27
27
  currency: z.ZodLiteral<"USD">;
28
28
  windowDays: z.ZodNumber;
29
29
  mode: z.ZodEnum<{
30
- "local-logs": "local-logs";
31
- connected: "connected";
32
30
  mixed: "mixed";
31
+ connected: "connected";
32
+ "local-logs": "local-logs";
33
33
  }>;
34
34
  financials: z.ZodObject<{
35
35
  subscriptionCommitted: z.ZodObject<{
@@ -47,8 +47,8 @@ export declare const receiptShareCardV0Schema: z.ZodObject<{
47
47
  providerBilled: z.ZodObject<{
48
48
  amountUsd: z.ZodNullable<z.ZodNumber>;
49
49
  financialEvidence: z.ZodEnum<{
50
- missing: "missing";
51
50
  verified: "verified";
51
+ missing: "missing";
52
52
  }>;
53
53
  }, z.core.$strict>;
54
54
  blended: z.ZodNull;
@@ -58,9 +58,9 @@ export declare const receiptShareCardV0Schema: z.ZodObject<{
58
58
  recordCount: z.ZodNumber;
59
59
  confidence: z.ZodEnum<{
60
60
  estimated: "estimated";
61
- missing: "missing";
62
61
  verified: "verified";
63
62
  detected_unverified: "detected_unverified";
63
+ missing: "missing";
64
64
  }>;
65
65
  cuts: z.ZodArray<z.ZodObject<{
66
66
  template: z.ZodEnum<{
@@ -89,9 +89,9 @@ export declare const receiptEmailRequestV0Schema: z.ZodObject<{
89
89
  currency: z.ZodLiteral<"USD">;
90
90
  windowDays: z.ZodNumber;
91
91
  mode: z.ZodEnum<{
92
- "local-logs": "local-logs";
93
- connected: "connected";
94
92
  mixed: "mixed";
93
+ connected: "connected";
94
+ "local-logs": "local-logs";
95
95
  }>;
96
96
  financials: z.ZodObject<{
97
97
  subscriptionCommitted: z.ZodObject<{
@@ -109,8 +109,8 @@ export declare const receiptEmailRequestV0Schema: z.ZodObject<{
109
109
  providerBilled: z.ZodObject<{
110
110
  amountUsd: z.ZodNullable<z.ZodNumber>;
111
111
  financialEvidence: z.ZodEnum<{
112
- missing: "missing";
113
112
  verified: "verified";
113
+ missing: "missing";
114
114
  }>;
115
115
  }, z.core.$strict>;
116
116
  blended: z.ZodNull;
@@ -120,9 +120,9 @@ export declare const receiptEmailRequestV0Schema: z.ZodObject<{
120
120
  recordCount: z.ZodNumber;
121
121
  confidence: z.ZodEnum<{
122
122
  estimated: "estimated";
123
- missing: "missing";
124
123
  verified: "verified";
125
124
  detected_unverified: "detected_unverified";
125
+ missing: "missing";
126
126
  }>;
127
127
  cuts: z.ZodArray<z.ZodObject<{
128
128
  template: z.ZodEnum<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-finops/core",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "funding": "https://asktilden.com",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",