@sunaiva/gate 1.1.0 → 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 (104) hide show
  1. package/BUSINESS_LICENSE.md +70 -70
  2. package/CHANGELOG.md +254 -148
  3. package/LICENSE +0 -0
  4. package/README.DRAFT.md +418 -0
  5. package/README.md +46 -26
  6. package/README.md.bak-v1.0.0-stale-MIT +59 -0
  7. package/SUPPORT.md +75 -0
  8. package/TIER_DEFINITIONS.md +161 -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 +1 -1
  15. package/dist/config/loader.js +1 -1
  16. package/dist/config/loader.js.map +1 -1
  17. package/dist/engine/backend-client.d.ts +0 -0
  18. package/dist/engine/backend-client.d.ts.map +1 -1
  19. package/dist/engine/backend-client.js +2 -2
  20. package/dist/engine/backend-client.js.map +1 -1
  21. package/dist/engine/hmac-verifier.d.ts +19 -0
  22. package/dist/engine/hmac-verifier.d.ts.map +1 -1
  23. package/dist/engine/hmac-verifier.js +1 -3
  24. package/dist/engine/hmac-verifier.js.map +1 -1
  25. package/dist/engine/immutability.d.ts +0 -0
  26. package/dist/engine/immutability.d.ts.map +1 -1
  27. package/dist/engine/immutability.js +0 -0
  28. package/dist/engine/immutability.js.map +1 -1
  29. package/dist/engine/pattern-matcher.d.ts +0 -0
  30. package/dist/engine/pattern-matcher.d.ts.map +1 -1
  31. package/dist/engine/pattern-matcher.js +0 -0
  32. package/dist/engine/pattern-matcher.js.map +1 -1
  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 +21 -4
  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 +1 -1
  39. package/dist/engine/session-state.js +0 -0
  40. package/dist/engine/session-state.js.map +1 -1
  41. package/dist/engine/ship-confidence-gate.d.ts +48 -0
  42. package/dist/engine/ship-confidence-gate.d.ts.map +1 -1
  43. package/dist/engine/ship-confidence-gate.js +2 -2
  44. package/dist/engine/ship-confidence-gate.js.map +1 -1
  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 +101 -23
  67. package/dist/rules/categories.json +0 -0
  68. package/dist/rules/presets.json +0 -0
  69. package/dist/rules/rules.json +257 -178
  70. package/dist/tools/audit.d.ts +0 -0
  71. package/dist/tools/audit.d.ts.map +1 -1
  72. package/dist/tools/audit.js +0 -0
  73. package/dist/tools/audit.js.map +1 -1
  74. package/dist/tools/bypass.d.ts +0 -0
  75. package/dist/tools/bypass.d.ts.map +1 -1
  76. package/dist/tools/bypass.js +1 -1
  77. package/dist/tools/bypass.js.map +1 -1
  78. package/dist/tools/export-attestation.d.ts +45 -0
  79. package/dist/tools/export-attestation.d.ts.map +1 -0
  80. package/dist/tools/export-attestation.js +152 -0
  81. package/dist/tools/export-attestation.js.map +1 -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 +6 -0
  87. package/dist/tools/ship-confidence.d.ts.map +1 -1
  88. package/dist/tools/ship-confidence.js +0 -0
  89. package/dist/tools/ship-confidence.js.map +1 -1
  90. package/dist/tools/update.d.ts +0 -0
  91. package/dist/tools/update.d.ts.map +1 -1
  92. package/dist/tools/update.js +1 -1
  93. package/dist/tools/update.js.map +1 -1
  94. package/dist/tools/validate.d.ts +0 -0
  95. package/dist/tools/validate.d.ts.map +1 -1
  96. package/dist/tools/validate.js +1 -1
  97. package/dist/tools/validate.js.map +1 -1
  98. package/dist/types/backend.d.ts +1 -1
  99. package/dist/types/backend.d.ts.map +1 -1
  100. package/dist/types/backend.js +1 -1
  101. package/dist/types/backend.js.map +1 -1
  102. package/package.json +84 -73
  103. package/dist/index.d.ts.map +0 -1
  104. package/dist/index.js.map +0 -1
@@ -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.0";
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) {
@@ -96,7 +103,9 @@ function runSmokeTest() {
96
103
  try {
97
104
  const raw = fs.readFileSync(rulesPath, "utf-8");
98
105
  const parsed = JSON.parse(raw);
99
- const flat = Array.isArray(parsed) ? parsed : Object.values(parsed).flat();
106
+ const flat = Array.isArray(parsed)
107
+ ? parsed
108
+ : Object.values(parsed).flat();
100
109
  allRules = flat;
101
110
  totalRules = flat.length;
102
111
  constitutionalCount = flat.filter((r) => r && (r.enforcement === "constitutional" || r.constitutional === true)).length;
@@ -179,7 +188,9 @@ function runSmokeTest() {
179
188
  }
180
189
  else {
181
190
  ok = result.allowed && !wasHard;
182
- detail = ok ? "ALLOW" : `expected ALLOW; got violations=${result.violations.map((v) => v.id).join(",")}`;
191
+ detail = ok
192
+ ? "ALLOW"
193
+ : `expected ALLOW; got violations=${result.violations.map((v) => v.id).join(",")}`;
183
194
  }
184
195
  checks.push({ label: `Live eval (${tc.label})`, ok, detail });
185
196
  }
@@ -214,7 +225,7 @@ function runSmokeTest() {
214
225
  }
215
226
  console.log(`Status: ${allOk ? "HEALTHY" : missingFiles ? "DEGRADED — MISSING FILES" : "DEGRADED"}`);
216
227
  console.log(`Version: ${PKG_VERSION}`);
217
- console.log(`Issues: https://github.com/Kinan27/sunaiva-gate/issues`);
228
+ console.log(`Support: support@sunaiva.ai`);
218
229
  // Exit code alignment with B3 / §6.1 of the brief:
219
230
  // 0 = HEALTHY
220
231
  // 1 = DEGRADED (legacy — any failing check)
@@ -232,27 +243,93 @@ async function main() {
232
243
  process.exit(0);
233
244
  }
234
245
  if (argv.includes("--help") || argv.includes("-h")) {
235
- console.log(`@sunaiva/gate v${PKG_VERSION} — MCP enforcement layer for AI agent rules
236
-
237
- Usage:
238
- npx @sunaiva/gate Run as MCP server (stdio transport)
239
- npx @sunaiva/gate --smoke-test Run a local health check and exit
240
- npx @sunaiva/gate --version Print version and exit
241
- npx @sunaiva/gate --help Show this message
242
-
243
- Environment:
244
- DISABLE_SUNAIVA_GATE=1 Kill-switch: server short-circuits to allow-all
245
- SUNAIVA_GATE_DRY_RUN=1 Dry-run: evaluate but never block (reports would_have_blocked)
246
-
247
- MCP integration: add the command to your client's mcpServers config.
248
- Docs: https://github.com/Kinan27/sunaiva-gate#readme
249
- Issues: https://github.com/Kinan27/sunaiva-gate/issues
250
- Support: support@sunaivadigital.com`);
246
+ console.log(`@sunaiva/gate v${PKG_VERSION} — MCP enforcement layer for AI agent rules
247
+
248
+ Usage:
249
+ npx @sunaiva/gate Run as MCP server (stdio transport)
250
+ npx @sunaiva/gate --smoke-test Run a local health check and exit
251
+ npx @sunaiva/gate --version Print version and exit
252
+ npx @sunaiva/gate --help Show this message
253
+
254
+ Environment:
255
+ DISABLE_SUNAIVA_GATE=1 Kill-switch: server short-circuits to allow-all
256
+ SUNAIVA_GATE_DRY_RUN=1 Dry-run: evaluate but never block (reports would_have_blocked)
257
+
258
+ MCP integration: add the command to your client's mcpServers config.
259
+ Docs: https://sunaivacore.io/products/gate
260
+ Support: support@sunaiva.ai`);
251
261
  process.exit(0);
252
262
  }
253
263
  if (argv.includes("--smoke-test")) {
254
264
  process.exit(runSmokeTest());
255
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
+ }
256
333
  // --ship-confidence <artifact-id> — standalone CLI gate invocation.
257
334
  // Exit 0 = allow, 2 = block, 3 = internal error (fail-OPEN logged).
258
335
  // Owned by B2; documented by B5.
@@ -317,8 +394,8 @@ function failClosed(err) {
317
394
  // Best-effort audit write — never block the exit on I/O.
318
395
  try {
319
396
  // Lazy import: keep --help / --version paths from pulling in audit code.
320
- // eslint-disable-next-line @typescript-eslint/no-var-requires
321
- import("./tools/audit.js").then((m) => {
397
+ import("./tools/audit.js")
398
+ .then((m) => {
322
399
  m.appendAudit({
323
400
  timestamp: new Date().toISOString(),
324
401
  gate_version: PKG_VERSION,
@@ -328,7 +405,8 @@ function failClosed(err) {
328
405
  error_message: msg.slice(0, 500),
329
406
  audit_status: isInvalidInput ? "INVALID_INPUT" : "INTERNAL_ERROR",
330
407
  });
331
- }).catch(() => { });
408
+ })
409
+ .catch(() => { });
332
410
  }
333
411
  catch {
334
412
  // Swallow audit errors — never block the exit code.
File without changes
File without changes