@sunaiva/gate 1.1.2 → 1.1.4

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.
Files changed (102) hide show
  1. package/BUSINESS_LICENSE.md +2 -2
  2. package/CHANGELOG.md +2 -2
  3. package/LICENSE +0 -0
  4. package/README.DRAFT.md +418 -0
  5. package/README.md +471 -451
  6. package/README.md.bak-v1.0.0-stale-MIT +0 -0
  7. package/SUPPORT.md +0 -0
  8. package/TIER_DEFINITIONS.md +0 -0
  9. package/dist/config/defaults.d.ts +30 -10
  10. package/dist/config/defaults.d.ts.map +1 -1
  11. package/dist/config/defaults.js +49 -26
  12. package/dist/config/defaults.js.map +1 -1
  13. package/dist/config/loader.d.ts +0 -0
  14. package/dist/config/loader.d.ts.map +0 -0
  15. package/dist/config/loader.js +0 -0
  16. package/dist/config/loader.js.map +0 -0
  17. package/dist/engine/backend-client.d.ts +0 -0
  18. package/dist/engine/backend-client.d.ts.map +0 -0
  19. package/dist/engine/backend-client.js +0 -0
  20. package/dist/engine/backend-client.js.map +0 -0
  21. package/dist/engine/hmac-verifier.d.ts +0 -0
  22. package/dist/engine/hmac-verifier.d.ts.map +0 -0
  23. package/dist/engine/hmac-verifier.js +0 -0
  24. package/dist/engine/hmac-verifier.js.map +0 -0
  25. package/dist/engine/immutability.d.ts +0 -0
  26. package/dist/engine/immutability.d.ts.map +0 -0
  27. package/dist/engine/immutability.js +0 -0
  28. package/dist/engine/immutability.js.map +0 -0
  29. package/dist/engine/pattern-matcher.d.ts +0 -0
  30. package/dist/engine/pattern-matcher.d.ts.map +0 -0
  31. package/dist/engine/pattern-matcher.js +0 -0
  32. package/dist/engine/pattern-matcher.js.map +0 -0
  33. package/dist/engine/rule-engine.d.ts +8 -1
  34. package/dist/engine/rule-engine.d.ts.map +1 -1
  35. package/dist/engine/rule-engine.js +18 -3
  36. package/dist/engine/rule-engine.js.map +1 -1
  37. package/dist/engine/session-state.d.ts +0 -0
  38. package/dist/engine/session-state.d.ts.map +0 -0
  39. package/dist/engine/session-state.js +0 -0
  40. package/dist/engine/session-state.js.map +0 -0
  41. package/dist/engine/ship-confidence-gate.d.ts +0 -0
  42. package/dist/engine/ship-confidence-gate.d.ts.map +0 -0
  43. package/dist/engine/ship-confidence-gate.js +0 -0
  44. package/dist/engine/ship-confidence-gate.js.map +0 -0
  45. package/dist/identity/first-run.d.ts +24 -0
  46. package/dist/identity/first-run.d.ts.map +1 -0
  47. package/dist/identity/first-run.js +88 -0
  48. package/dist/identity/first-run.js.map +1 -0
  49. package/dist/identity/nudge.d.ts +29 -0
  50. package/dist/identity/nudge.d.ts.map +1 -0
  51. package/dist/identity/nudge.js +74 -0
  52. package/dist/identity/nudge.js.map +1 -0
  53. package/dist/identity/premium-unlock.d.ts +30 -0
  54. package/dist/identity/premium-unlock.d.ts.map +1 -0
  55. package/dist/identity/premium-unlock.js +65 -0
  56. package/dist/identity/premium-unlock.js.map +1 -0
  57. package/dist/identity/register-client.d.ts +25 -0
  58. package/dist/identity/register-client.d.ts.map +1 -0
  59. package/dist/identity/register-client.js +48 -0
  60. package/dist/identity/register-client.js.map +1 -0
  61. package/dist/identity/telemetry.d.ts +64 -0
  62. package/dist/identity/telemetry.d.ts.map +1 -0
  63. package/dist/identity/telemetry.js +173 -0
  64. package/dist/identity/telemetry.js.map +1 -0
  65. package/dist/index.d.ts +0 -0
  66. package/dist/index.js +75 -1
  67. package/dist/rules/categories.json +0 -0
  68. package/dist/rules/presets.json +0 -0
  69. package/dist/rules/rules.json +153 -42
  70. package/dist/tools/audit.d.ts +0 -0
  71. package/dist/tools/audit.d.ts.map +0 -0
  72. package/dist/tools/audit.js +0 -0
  73. package/dist/tools/audit.js.map +0 -0
  74. package/dist/tools/bypass.d.ts +0 -0
  75. package/dist/tools/bypass.d.ts.map +0 -0
  76. package/dist/tools/bypass.js +0 -0
  77. package/dist/tools/bypass.js.map +0 -0
  78. package/dist/tools/export-attestation.d.ts +0 -0
  79. package/dist/tools/export-attestation.d.ts.map +0 -0
  80. package/dist/tools/export-attestation.js +0 -0
  81. package/dist/tools/export-attestation.js.map +0 -0
  82. package/dist/tools/rules.d.ts +0 -0
  83. package/dist/tools/rules.d.ts.map +0 -0
  84. package/dist/tools/rules.js +0 -0
  85. package/dist/tools/rules.js.map +0 -0
  86. package/dist/tools/ship-confidence.d.ts +0 -0
  87. package/dist/tools/ship-confidence.d.ts.map +0 -0
  88. package/dist/tools/ship-confidence.js +0 -0
  89. package/dist/tools/ship-confidence.js.map +0 -0
  90. package/dist/tools/update.d.ts +0 -0
  91. package/dist/tools/update.d.ts.map +0 -0
  92. package/dist/tools/update.js +0 -0
  93. package/dist/tools/update.js.map +0 -0
  94. package/dist/tools/validate.d.ts +0 -0
  95. package/dist/tools/validate.d.ts.map +0 -0
  96. package/dist/tools/validate.js +0 -0
  97. package/dist/tools/validate.js.map +0 -0
  98. package/dist/types/backend.d.ts +0 -0
  99. package/dist/types/backend.d.ts.map +0 -0
  100. package/dist/types/backend.js +0 -0
  101. package/dist/types/backend.js.map +0 -0
  102. package/package.json +2 -1
@@ -0,0 +1,74 @@
1
+ /**
2
+ * First-run nudge — opt-in registration prompt.
3
+ *
4
+ * Design constraints (v1.1.4):
5
+ * - CORE gate is FREE and NEVER email-gated. Gating the critical path = lockout class.
6
+ * - Email is captured at the VALUE layer (live rule feed, Shield-of-Health dashboard).
7
+ * - Nudge is skippable via SUNAIVA_NUDGE_OFF=1.
8
+ * - Nudge fires at most once per install (presence of ~/.sunaiva-gate/registered marker).
9
+ * - FAIL-OPEN: any error in this module must not surface to the caller.
10
+ */
11
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import { join } from "node:path";
14
+ const MARKER_DIR = join(homedir(), ".sunaiva-gate");
15
+ const MARKER_PATH = join(MARKER_DIR, "registered");
16
+ /**
17
+ * Returns true if the registration marker exists (user has already seen the nudge).
18
+ */
19
+ export function isNudgeComplete() {
20
+ try {
21
+ return existsSync(MARKER_PATH);
22
+ }
23
+ catch {
24
+ return true; // fail-open: if we can't read, skip the nudge
25
+ }
26
+ }
27
+ /**
28
+ * Writes the registration marker so the nudge does not fire again.
29
+ * Fail-open on any error.
30
+ */
31
+ export function markNudgeComplete() {
32
+ try {
33
+ if (!existsSync(MARKER_DIR)) {
34
+ mkdirSync(MARKER_DIR, { recursive: true });
35
+ }
36
+ writeFileSync(MARKER_PATH, new Date().toISOString(), "utf-8");
37
+ }
38
+ catch {
39
+ // fail-open: marker write failure is non-fatal
40
+ }
41
+ }
42
+ /**
43
+ * maybeShowNudge — print a friendly one-time registration prompt if:
44
+ * 1. SUNAIVA_NUDGE_OFF=1 is NOT set
45
+ * 2. The registered marker does not exist
46
+ * 3. stdout is a TTY (not piped — avoids polluting JSON output)
47
+ *
48
+ * FAIL-OPEN: any error is swallowed. This function NEVER throws.
49
+ */
50
+ export function maybeShowNudge() {
51
+ try {
52
+ if (process.env.SUNAIVA_NUDGE_OFF === "1")
53
+ return;
54
+ if (isNudgeComplete())
55
+ return;
56
+ // Only print to TTY to avoid polluting piped/MCP output
57
+ if (!process.stdout.isTTY) {
58
+ // In non-TTY mode write to stderr so the nudge does not break JSON output
59
+ process.stderr.write(`[sunaiva-gate] Register free at https://sunaivacore.io/gate/register ` +
60
+ `for the live rule feed + Shield-of-Health dashboard. ` +
61
+ `Skip with SUNAIVA_NUDGE_OFF=1.\n`);
62
+ }
63
+ else {
64
+ process.stdout.write(`\n[sunaiva-gate] Register free at https://sunaivacore.io/gate/register\n` +
65
+ ` for the live rule feed + Shield-of-Health dashboard.\n` +
66
+ ` Skip this message with: SUNAIVA_NUDGE_OFF=1\n\n`);
67
+ }
68
+ markNudgeComplete();
69
+ }
70
+ catch {
71
+ // fail-open: nudge errors are never surfaced
72
+ }
73
+ }
74
+ //# sourceMappingURL=nudge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nudge.js","sourceRoot":"","sources":["../../src/identity/nudge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,eAAe,CAAC,CAAC;AACpD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAEnD;;GAEG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,8CAA8C;IAC7D,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,aAAa,CAAC,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,GAAG;YAAE,OAAO;QAClD,IAAI,eAAe,EAAE;YAAE,OAAO;QAC9B,wDAAwD;QACxD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAC1B,0EAA0E;YAC1E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uEAAuE;gBACrE,uDAAuD;gBACvD,kCAAkC,CACrC,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,0EAA0E;gBACxE,0DAA0D;gBAC1D,mDAAmD,CACtD,CAAC;QACJ,CAAC;QACD,iBAAiB,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;IAC/C,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Premium rule unlock — fetch premium rules from backend when API key is present.
3
+ *
4
+ * Design constraints (v1.1.4):
5
+ * - Endpoint TBD: stubbed via SUNAIVA_RULES_ENDPOINT env var.
6
+ * - Default stub: https://sunaivacore.io/api/rules/premium (backend TBD).
7
+ * - On success: merges premium rules into the engine's in-memory rule set.
8
+ * - On any failure: falls back to local-only rules + logs to stderr.
9
+ * - 10s timeout — premium rules are fetched once at startup, not per-request.
10
+ * - FAIL-OPEN: a network failure never blocks the user from using the gate.
11
+ * - Only called when config.api_key is present (non-empty string).
12
+ */
13
+ import type { Rule } from "../engine/rule-engine.js";
14
+ export interface PremiumUnlockResult {
15
+ ok: boolean;
16
+ rules_fetched: number;
17
+ error?: string;
18
+ }
19
+ /**
20
+ * fetchPremiumRules — GET premium rules from the backend using the API key.
21
+ *
22
+ * On success: returns the rule array for the caller to merge into the engine.
23
+ * On any failure: returns fail-open result (ok:false, rules_fetched:0).
24
+ * NEVER throws.
25
+ */
26
+ export declare function fetchPremiumRules(apiKey: string): Promise<{
27
+ result: PremiumUnlockResult;
28
+ rules: Rule[];
29
+ }>;
30
+ //# sourceMappingURL=premium-unlock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"premium-unlock.d.ts","sourceRoot":"","sources":["../../src/identity/premium-unlock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAKrD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,MAAM,EAAE,mBAAmB,CAAC;IAAC,KAAK,EAAE,IAAI,EAAE,CAAA;CAAE,CAAC,CAoDzD"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Premium rule unlock — fetch premium rules from backend when API key is present.
3
+ *
4
+ * Design constraints (v1.1.4):
5
+ * - Endpoint TBD: stubbed via SUNAIVA_RULES_ENDPOINT env var.
6
+ * - Default stub: https://sunaivacore.io/api/rules/premium (backend TBD).
7
+ * - On success: merges premium rules into the engine's in-memory rule set.
8
+ * - On any failure: falls back to local-only rules + logs to stderr.
9
+ * - 10s timeout — premium rules are fetched once at startup, not per-request.
10
+ * - FAIL-OPEN: a network failure never blocks the user from using the gate.
11
+ * - Only called when config.api_key is present (non-empty string).
12
+ */
13
+ const DEFAULT_RULES_ENDPOINT = "https://sunaivacore.io/api/rules/premium";
14
+ const FETCH_TIMEOUT_MS = 10_000;
15
+ /**
16
+ * fetchPremiumRules — GET premium rules from the backend using the API key.
17
+ *
18
+ * On success: returns the rule array for the caller to merge into the engine.
19
+ * On any failure: returns fail-open result (ok:false, rules_fetched:0).
20
+ * NEVER throws.
21
+ */
22
+ export async function fetchPremiumRules(apiKey) {
23
+ if (!apiKey || apiKey.trim().length === 0) {
24
+ return {
25
+ result: { ok: false, rules_fetched: 0, error: "empty api_key" },
26
+ rules: [],
27
+ };
28
+ }
29
+ const endpoint = process.env.SUNAIVA_RULES_ENDPOINT ?? DEFAULT_RULES_ENDPOINT;
30
+ const controller = new AbortController();
31
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
32
+ try {
33
+ const response = await fetch(endpoint, {
34
+ method: "GET",
35
+ headers: {
36
+ Authorization: `Bearer ${apiKey}`,
37
+ "Accept": "application/json",
38
+ "X-Gate-Version": "1.1.4",
39
+ },
40
+ signal: controller.signal,
41
+ });
42
+ clearTimeout(timer);
43
+ if (!response.ok) {
44
+ const errMsg = `premium-unlock: backend returned ${response.status}`;
45
+ console.error(`[sunaiva-gate] ${errMsg} — falling back to local-only rules`);
46
+ return { result: { ok: false, rules_fetched: 0, error: errMsg }, rules: [] };
47
+ }
48
+ const data = (await response.json());
49
+ const rules = Array.isArray(data.rules) ? data.rules : [];
50
+ return {
51
+ result: { ok: true, rules_fetched: rules.length },
52
+ rules,
53
+ };
54
+ }
55
+ catch (err) {
56
+ clearTimeout(timer);
57
+ const errMsg = err instanceof Error ? err.message : String(err);
58
+ console.error(`[sunaiva-gate] premium-unlock fetch failed (fail-OPEN): ${errMsg} — local-only rules active`);
59
+ return {
60
+ result: { ok: false, rules_fetched: 0, error: errMsg },
61
+ rules: [],
62
+ };
63
+ }
64
+ }
65
+ //# sourceMappingURL=premium-unlock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"premium-unlock.js","sourceRoot":"","sources":["../../src/identity/premium-unlock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,sBAAsB,GAAG,0CAA0C,CAAC;AAC1E,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAQhC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAc;IAEd,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO;YACL,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE;YAC/D,KAAK,EAAE,EAAE;SACV,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GACZ,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,sBAAsB,CAAC;IAE/D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAErE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YACrC,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,MAAM,EAAE;gBACjC,QAAQ,EAAE,kBAAkB;gBAC5B,gBAAgB,EAAE,OAAO;aAC1B;YACD,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QAEH,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,oCAAoC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrE,OAAO,CAAC,KAAK,CAAC,kBAAkB,MAAM,qCAAqC,CAAC,CAAC;YAC7E,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC/E,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;QAC3D,MAAM,KAAK,GAAW,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAElE,OAAO;YACL,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,CAAC,MAAM,EAAE;YACjD,KAAK;SACN,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,MAAM,GACV,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnD,OAAO,CAAC,KAAK,CACX,2DAA2D,MAAM,4BAA4B,CAC9F,CAAC;QACF,OAAO;YACL,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE;YACtD,KAAK,EAAE,EAAE;SACV,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Registration client — POST {email} to the Sunaiva registration endpoint.
3
+ *
4
+ * Design constraints (v1.1.4):
5
+ * - Endpoint TBD: stubbed via SUNAIVA_REGISTER_ENDPOINT env var.
6
+ * - Default stub: https://sunaivacore.io/api/register (backend TBD).
7
+ * - Returns {api_key, ok} or throws — CALLER must catch and fail-open.
8
+ * - 5s timeout to avoid hanging in tool-use critical paths.
9
+ * - NEVER includes PII beyond the email the user voluntarily supplies.
10
+ * - FAIL-OPEN: caller wraps in try/catch.
11
+ */
12
+ export interface RegisterResult {
13
+ ok: boolean;
14
+ api_key?: string;
15
+ message?: string;
16
+ }
17
+ /**
18
+ * registerEmail — POST {email} to the registration endpoint.
19
+ *
20
+ * Throws on network error, non-2xx, or timeout. Caller must catch and fail-open.
21
+ *
22
+ * @param email User email (voluntarily supplied at value capture layer).
23
+ */
24
+ export declare function registerEmail(email: string): Promise<RegisterResult>;
25
+ //# sourceMappingURL=register-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-client.d.ts","sourceRoot":"","sources":["../../src/identity/register-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAoC1E"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Registration client — POST {email} to the Sunaiva registration endpoint.
3
+ *
4
+ * Design constraints (v1.1.4):
5
+ * - Endpoint TBD: stubbed via SUNAIVA_REGISTER_ENDPOINT env var.
6
+ * - Default stub: https://sunaivacore.io/api/register (backend TBD).
7
+ * - Returns {api_key, ok} or throws — CALLER must catch and fail-open.
8
+ * - 5s timeout to avoid hanging in tool-use critical paths.
9
+ * - NEVER includes PII beyond the email the user voluntarily supplies.
10
+ * - FAIL-OPEN: caller wraps in try/catch.
11
+ */
12
+ const DEFAULT_REGISTER_ENDPOINT = "https://sunaivacore.io/api/register";
13
+ const REGISTER_TIMEOUT_MS = 5_000;
14
+ /**
15
+ * registerEmail — POST {email} to the registration endpoint.
16
+ *
17
+ * Throws on network error, non-2xx, or timeout. Caller must catch and fail-open.
18
+ *
19
+ * @param email User email (voluntarily supplied at value capture layer).
20
+ */
21
+ export async function registerEmail(email) {
22
+ const endpoint = process.env.SUNAIVA_REGISTER_ENDPOINT ?? DEFAULT_REGISTER_ENDPOINT;
23
+ const controller = new AbortController();
24
+ const timer = setTimeout(() => controller.abort(), REGISTER_TIMEOUT_MS);
25
+ try {
26
+ const response = await fetch(endpoint, {
27
+ method: "POST",
28
+ headers: { "Content-Type": "application/json" },
29
+ body: JSON.stringify({ email, source: "sunaiva-gate", version: "1.1.4" }),
30
+ signal: controller.signal,
31
+ });
32
+ clearTimeout(timer);
33
+ if (!response.ok) {
34
+ throw new Error(`Registration endpoint returned ${response.status}`);
35
+ }
36
+ const data = (await response.json());
37
+ return {
38
+ ok: true,
39
+ api_key: data.api_key,
40
+ message: data.message,
41
+ };
42
+ }
43
+ catch (err) {
44
+ clearTimeout(timer);
45
+ throw err; // re-throw so caller can fail-open
46
+ }
47
+ }
48
+ //# sourceMappingURL=register-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-client.js","sourceRoot":"","sources":["../../src/identity/register-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,yBAAyB,GAAG,qCAAqC,CAAC;AACxE,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAQlC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa;IAC/C,MAAM,QAAQ,GACZ,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,yBAAyB,CAAC;IAErE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,mBAAmB,CAAC,CAAC;IAExE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YACrC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;YACzE,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QAEH,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAIlC,CAAC;QAEF,OAAO;YACL,EAAE,EAAE,IAAI;YACR,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,GAAG,CAAC,CAAC,mCAAmC;IAChD,CAAC;AACH,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Opt-in anonymous telemetry emit + install-tracking layer.
3
+ *
4
+ * Design constraints (v1.1.4):
5
+ * - NO PII, NO file content, NO action content.
6
+ * - Emits: version, gate_version, agent type, os.platform(), os.release(), counts.
7
+ * - Off by default: only fires when user has opted in (SUNAIVA_TELEMETRY_ON=1).
8
+ * - Kill-switch: SUNAIVA_TELEMETRY_OFF=1 overrides any opt-in.
9
+ * - 2s timeout — telemetry must never delay gate decisions.
10
+ * - FAIL-OPEN: any error is swallowed. This function NEVER throws.
11
+ * - Endpoint stubbed via SUNAIVA_TELEMETRY_ENDPOINT env var.
12
+ *
13
+ * Install-tracking layer (v1.1.4 additions):
14
+ * - anonymousFingerprint(): SHA-256 of stable machine identifiers. NO PII, NO IP.
15
+ * - isGenesisInternal(): detects Genesis development environment.
16
+ * - emitFirstRunIfNeeded(): single-shot install event, fire-and-forget. 3s timeout.
17
+ * Opt-out: SUNAIVA_GATE_TELEMETRY=0 disables completely.
18
+ * Marker: ~/.sunaiva-gate/first-run.json prevents duplicate events.
19
+ */
20
+ export interface TelemetryEvent {
21
+ /** Gate version emitting this event */
22
+ gate_version: string;
23
+ /** Agent type (claude-code, cursor, etc.) or "unknown" */
24
+ agent: string;
25
+ /** os.platform() — linux, win32, darwin */
26
+ os_platform: string;
27
+ /** os.release() — kernel version */
28
+ os_release: string;
29
+ /** Number of violations in the evaluation (no content) */
30
+ violation_count: number;
31
+ /** Number of warnings in the evaluation (no content) */
32
+ warning_count: number;
33
+ /** Number of rules active in this session */
34
+ active_rule_count: number;
35
+ /** ISO timestamp */
36
+ ts: string;
37
+ }
38
+ /**
39
+ * emitEvaluation — send an anonymous telemetry event.
40
+ *
41
+ * Only fires when SUNAIVA_TELEMETRY_ON=1 AND SUNAIVA_TELEMETRY_OFF is not set.
42
+ * FAIL-OPEN: all errors are swallowed. Never throws. Never awaited by caller
43
+ * in a blocking way (fire-and-forget via void).
44
+ */
45
+ export declare function emitEvaluation(eventSummary: {
46
+ agent: string;
47
+ violation_count: number;
48
+ warning_count: number;
49
+ active_rule_count: number;
50
+ }): void;
51
+ /**
52
+ * emitFirstRunIfNeeded — fire a single anonymous "first_run" install event.
53
+ *
54
+ * Privacy posture:
55
+ * - Payload contains: event="first_run", anonymous fingerprint (32-hex),
56
+ * gate_version, node_version, os_platform, os_release,
57
+ * is_genesis_internal, timestamp. NO PII, NO IP, NO email.
58
+ * - Runs at most once per machine (marker file ~/.sunaiva-gate/first-run.json).
59
+ * - Opt-out: SUNAIVA_GATE_TELEMETRY=0 disables entirely.
60
+ * - Fire-and-forget: caller must NOT await. Never throws, never logs to stderr.
61
+ * - 3-second timeout — must not delay gate startup.
62
+ */
63
+ export declare function emitFirstRunIfNeeded(version: string): Promise<void>;
64
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/identity/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkBH,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,aAAa,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB;IACpB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GAAG,IAAI,CAUP;AAuFD;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CzE"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Opt-in anonymous telemetry emit + install-tracking layer.
3
+ *
4
+ * Design constraints (v1.1.4):
5
+ * - NO PII, NO file content, NO action content.
6
+ * - Emits: version, gate_version, agent type, os.platform(), os.release(), counts.
7
+ * - Off by default: only fires when user has opted in (SUNAIVA_TELEMETRY_ON=1).
8
+ * - Kill-switch: SUNAIVA_TELEMETRY_OFF=1 overrides any opt-in.
9
+ * - 2s timeout — telemetry must never delay gate decisions.
10
+ * - FAIL-OPEN: any error is swallowed. This function NEVER throws.
11
+ * - Endpoint stubbed via SUNAIVA_TELEMETRY_ENDPOINT env var.
12
+ *
13
+ * Install-tracking layer (v1.1.4 additions):
14
+ * - anonymousFingerprint(): SHA-256 of stable machine identifiers. NO PII, NO IP.
15
+ * - isGenesisInternal(): detects Genesis development environment.
16
+ * - emitFirstRunIfNeeded(): single-shot install event, fire-and-forget. 3s timeout.
17
+ * Opt-out: SUNAIVA_GATE_TELEMETRY=0 disables completely.
18
+ * Marker: ~/.sunaiva-gate/first-run.json prevents duplicate events.
19
+ */
20
+ import { createHash } from "node:crypto";
21
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
22
+ import { homedir, hostname, platform, release, userInfo, } from "node:os";
23
+ import { join } from "node:path";
24
+ const GATE_VERSION = "1.1.4";
25
+ const DEFAULT_TELEMETRY_ENDPOINT = "https://gate-telemetry.kinan-ae7.workers.dev/v1/events";
26
+ const TELEMETRY_TIMEOUT_MS = 2_000;
27
+ /**
28
+ * emitEvaluation — send an anonymous telemetry event.
29
+ *
30
+ * Only fires when SUNAIVA_TELEMETRY_ON=1 AND SUNAIVA_TELEMETRY_OFF is not set.
31
+ * FAIL-OPEN: all errors are swallowed. Never throws. Never awaited by caller
32
+ * in a blocking way (fire-and-forget via void).
33
+ */
34
+ export function emitEvaluation(eventSummary) {
35
+ // Kill-switch always wins
36
+ if (process.env.SUNAIVA_TELEMETRY_OFF === "1")
37
+ return;
38
+ // Must be explicitly opted in — off by default (CORE is free, no data harvesting)
39
+ if (process.env.SUNAIVA_TELEMETRY_ON !== "1")
40
+ return;
41
+ // Fire-and-forget — do not await, do not block gate decisions
42
+ void _sendTelemetry(eventSummary).catch(() => {
43
+ // fail-open: swallow all errors
44
+ });
45
+ }
46
+ async function _sendTelemetry(eventSummary) {
47
+ const endpoint = process.env.SUNAIVA_TELEMETRY_ENDPOINT ?? DEFAULT_TELEMETRY_ENDPOINT;
48
+ const payload = {
49
+ gate_version: GATE_VERSION,
50
+ agent: eventSummary.agent,
51
+ os_platform: platform(),
52
+ os_release: release(),
53
+ violation_count: eventSummary.violation_count,
54
+ warning_count: eventSummary.warning_count,
55
+ active_rule_count: eventSummary.active_rule_count,
56
+ ts: new Date().toISOString(),
57
+ };
58
+ const controller = new AbortController();
59
+ const timer = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
60
+ try {
61
+ await fetch(endpoint, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/json" },
64
+ body: JSON.stringify(payload),
65
+ signal: controller.signal,
66
+ });
67
+ }
68
+ finally {
69
+ clearTimeout(timer);
70
+ }
71
+ }
72
+ // ---------------------------------------------------------------------------
73
+ // Install-tracking layer (v1.1.4)
74
+ // ---------------------------------------------------------------------------
75
+ const FIRST_RUN_TIMEOUT_MS = 3_000;
76
+ const FIRST_RUN_MARKER_DIR = join(homedir(), ".sunaiva-gate");
77
+ const FIRST_RUN_MARKER_PATH = join(FIRST_RUN_MARKER_DIR, "first-run.json");
78
+ /**
79
+ * anonymousFingerprint — derive a stable, anonymous machine identifier.
80
+ *
81
+ * Privacy posture:
82
+ * - Input: hostname + username + platform + homedir (all local, no network).
83
+ * - Output: first 32 hex chars of SHA-256 — sufficient to de-duplicate
84
+ * installs, NOT sufficient to reverse-identify a user.
85
+ * - NO IP address, NO email, NO file paths, NO system serial numbers.
86
+ * - Value is stable across gate upgrades on the same machine.
87
+ * - On error returns "unknown" (fail-open).
88
+ */
89
+ function anonymousFingerprint() {
90
+ try {
91
+ const raw = [
92
+ hostname(),
93
+ userInfo().username,
94
+ platform(),
95
+ homedir(),
96
+ ].join("|");
97
+ return createHash("sha256").update(raw).digest("hex").slice(0, 32);
98
+ }
99
+ catch {
100
+ return "unknown";
101
+ }
102
+ }
103
+ /**
104
+ * isGenesisInternal — returns true when running inside a Genesis development
105
+ * environment.
106
+ *
107
+ * Privacy posture:
108
+ * - Reads only environment variables, no network calls.
109
+ * - Allows the telemetry receiver to filter Genesis-internal noise from
110
+ * real-world install events without any PII signal.
111
+ */
112
+ function isGenesisInternal() {
113
+ if (process.env.SUNAIVA_GATE_INTERNAL)
114
+ return true;
115
+ if (process.env.GENESIS_SESSION_ID)
116
+ return true;
117
+ const projectDir = process.env.CLAUDE_PROJECT_DIR ?? "";
118
+ if (projectDir.includes("genesis-system"))
119
+ return true;
120
+ return false;
121
+ }
122
+ /**
123
+ * emitFirstRunIfNeeded — fire a single anonymous "first_run" install event.
124
+ *
125
+ * Privacy posture:
126
+ * - Payload contains: event="first_run", anonymous fingerprint (32-hex),
127
+ * gate_version, node_version, os_platform, os_release,
128
+ * is_genesis_internal, timestamp. NO PII, NO IP, NO email.
129
+ * - Runs at most once per machine (marker file ~/.sunaiva-gate/first-run.json).
130
+ * - Opt-out: SUNAIVA_GATE_TELEMETRY=0 disables entirely.
131
+ * - Fire-and-forget: caller must NOT await. Never throws, never logs to stderr.
132
+ * - 3-second timeout — must not delay gate startup.
133
+ */
134
+ export async function emitFirstRunIfNeeded(version) {
135
+ try {
136
+ // Respect explicit opt-out
137
+ if (process.env.SUNAIVA_GATE_TELEMETRY === "0")
138
+ return;
139
+ // Single-shot per machine
140
+ if (existsSync(FIRST_RUN_MARKER_PATH))
141
+ return;
142
+ const payload = {
143
+ event: "first_run",
144
+ fingerprint: anonymousFingerprint(),
145
+ gate_version: version,
146
+ node_version: process.version,
147
+ os_platform: platform(),
148
+ os_release: release(),
149
+ is_genesis_internal: isGenesisInternal(),
150
+ timestamp: new Date().toISOString(),
151
+ };
152
+ const controller = new AbortController();
153
+ const timer = setTimeout(() => controller.abort(), FIRST_RUN_TIMEOUT_MS);
154
+ try {
155
+ await fetch(DEFAULT_TELEMETRY_ENDPOINT, {
156
+ method: "POST",
157
+ headers: { "Content-Type": "application/json" },
158
+ body: JSON.stringify(payload),
159
+ signal: controller.signal,
160
+ });
161
+ }
162
+ finally {
163
+ clearTimeout(timer);
164
+ }
165
+ // Write marker only after a successful (non-throwing) POST attempt
166
+ mkdirSync(FIRST_RUN_MARKER_DIR, { recursive: true });
167
+ writeFileSync(FIRST_RUN_MARKER_PATH, JSON.stringify({ first_run_at: new Date().toISOString() }));
168
+ }
169
+ catch {
170
+ // FAIL-OPEN: telemetry must never surface errors to the caller.
171
+ }
172
+ }
173
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/identity/telemetry.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
File without changes
package/dist/index.js CHANGED
@@ -12,10 +12,14 @@ import { ShipConfidenceGate } from "./engine/ship-confidence-gate.js";
12
12
  import { evaluateAction } from "./engine/rule-engine.js";
13
13
  import { getConstitutionalRuleIds } from "./engine/immutability.js";
14
14
  import { DEFAULT_CONFIG } from "./config/defaults.js";
15
+ import { runBridge } from "./events/bridge.js";
16
+ import { SUPPORTED_AGENTS } from "./installer/detect.js";
17
+ import { maybeShowNudge } from "./identity/nudge.js";
18
+ import { emitFirstRunIfNeeded } from "./identity/first-run.js";
15
19
  import * as fs from "node:fs";
16
20
  import * as path from "node:path";
17
21
  import { fileURLToPath } from "node:url";
18
- const PKG_VERSION = "1.1.2";
22
+ const PKG_VERSION = "1.1.4";
19
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
24
  const server = new Server({ name: "sunaiva-gate", version: PKG_VERSION }, { capabilities: { tools: {} } });
21
25
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -61,6 +65,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
61
65
  ],
62
66
  }));
63
67
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
68
+ // Fire-and-forget first-run ping (MCP server path only — never --smoke-test,
69
+ // --version, or --mcp-bridge). Self-throttles via marker file after first call.
70
+ void emitFirstRunIfNeeded(PKG_VERSION);
64
71
  const { name, arguments: args } = req.params;
65
72
  try {
66
73
  switch (name) {
@@ -256,6 +263,73 @@ Support: support@sunaiva.ai`);
256
263
  if (argv.includes("--smoke-test")) {
257
264
  process.exit(runSmokeTest());
258
265
  }
266
+ // --mcp-bridge <agent> — production hook path.
267
+ // Every installer shim runs `npx @sunaiva/gate --mcp-bridge <agent>`.
268
+ // The host agent pipes a raw JSON event to stdin; we evaluate it through
269
+ // the wired rule engine (via runBridge → handleValidateAction) and write
270
+ // the verdict JSON to stdout. Exit 0 always (fail-OPEN contract: a gate
271
+ // error must never crash the host agent).
272
+ const bridgeIdx = argv.indexOf("--mcp-bridge");
273
+ if (bridgeIdx !== -1) {
274
+ // v1.1.4: show first-run nudge (fail-open, skippable via SUNAIVA_NUDGE_OFF=1)
275
+ maybeShowNudge();
276
+ const agentArg = argv[bridgeIdx + 1];
277
+ if (!agentArg || !SUPPORTED_AGENTS.includes(agentArg)) {
278
+ const supported = SUPPORTED_AGENTS.join(", ");
279
+ console.error(`[sunaiva-gate v${PKG_VERSION}] --mcp-bridge requires a valid agent name (${supported})`);
280
+ // Fail-OPEN: print an allow envelope so the host can continue.
281
+ console.log(JSON.stringify({
282
+ ok: true,
283
+ event: "unknown",
284
+ source_agent: agentArg ?? "unknown",
285
+ tool: undefined,
286
+ action: undefined,
287
+ session_id: undefined,
288
+ decision: "allow",
289
+ reason: "invalid agent argument — fail-OPEN",
290
+ fail_open: true,
291
+ gate_version: PKG_VERSION,
292
+ }));
293
+ process.exit(0);
294
+ }
295
+ // Read the raw event payload from stdin (the host pipes it in one shot).
296
+ let raw = null;
297
+ try {
298
+ const chunks = [];
299
+ for await (const chunk of process.stdin) {
300
+ chunks.push(chunk);
301
+ }
302
+ const text = Buffer.concat(chunks).toString("utf-8").trim();
303
+ if (text.length > 0) {
304
+ raw = JSON.parse(text);
305
+ }
306
+ }
307
+ catch {
308
+ // Malformed or empty stdin: fall through with raw=null; runBridge/
309
+ // normalizeIncoming handles unknown shapes gracefully (fail-OPEN).
310
+ }
311
+ try {
312
+ const verdict = await runBridge(raw, agentArg, PKG_VERSION);
313
+ console.log(JSON.stringify(verdict));
314
+ }
315
+ catch (err) {
316
+ // runBridge promises never to throw, but be defensive anyway.
317
+ console.error(`[sunaiva-gate v${PKG_VERSION}] bridge unexpected error (fail-OPEN):`, err instanceof Error ? err.message : String(err));
318
+ console.log(JSON.stringify({
319
+ ok: true,
320
+ event: "unknown",
321
+ source_agent: agentArg,
322
+ tool: undefined,
323
+ action: undefined,
324
+ session_id: undefined,
325
+ decision: "allow",
326
+ reason: `bridge unexpected error (fail-OPEN): ${err instanceof Error ? err.message : String(err)}`,
327
+ fail_open: true,
328
+ gate_version: PKG_VERSION,
329
+ }));
330
+ }
331
+ process.exit(0);
332
+ }
259
333
  // --ship-confidence <artifact-id> — standalone CLI gate invocation.
260
334
  // Exit 0 = allow, 2 = block, 3 = internal error (fail-OPEN logged).
261
335
  // Owned by B2; documented by B5.
File without changes
File without changes