loki-mode 7.46.0 → 7.48.0
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/README.md +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +113 -0
- package/autonomy/crash.sh +47 -21
- package/autonomy/loki +50 -27
- package/autonomy/run.sh +468 -5
- package/autonomy/spec-interrogation.sh +550 -0
- package/autonomy/telemetry.sh +28 -8
- package/bin/postinstall.js +22 -10
- package/dashboard/__init__.py +1 -1
- package/dashboard/auth.py +117 -2
- package/dashboard/telemetry.py +34 -6
- package/docs/ACKNOWLEDGEMENTS.md +1 -1
- package/docs/COMPETITIVE-ANALYSIS.md +1 -1
- package/docs/INSTALLATION.md +10 -3
- package/docs/OPEN-CORE-BOUNDARY.md +6 -5
- package/docs/P2-SPEC-ROBUSTNESS-PLAN.md +192 -0
- package/docs/PRIVACY.md +82 -24
- package/docs/R9-OPEN-CORE-HOOKS-PLAN.md +2 -2
- package/docs/auto-claude-comparison.md +2 -2
- package/docs/certification/README.md +1 -1
- package/docs/competitive/bolt-new-analysis.md +1 -1
- package/docs/competitive/emergence-others-analysis.md +6 -6
- package/docs/competitive/replit-lovable-analysis.md +4 -4
- package/docs/enterprise/security.md +43 -3
- package/docs/show-hn-post.md +1 -1
- package/loki-ts/dist/loki.js +30 -30
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-CKUOsWZW.js → AdminPage-CcCJ0Sjt.js} +1 -1
- package/web-app/dist/assets/{Avatar-CL9Id9Hi.js → Avatar-DK8kmayw.js} +1 -1
- package/web-app/dist/assets/{Badge-B12zwlD7.js → Badge-4uAWnemi.js} +1 -1
- package/web-app/dist/assets/{Button-CFLVoduT.js → Button-BBMk33tk.js} +1 -1
- package/web-app/dist/assets/ComparePage-bt9rwvST.js +1 -0
- package/web-app/dist/assets/{GitHubIssuesPanel-CSitxtAX.js → GitHubIssuesPanel-WDbH47UM.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BIT06FRo.js → GitHubPRsPanel-C2CiYtTx.js} +1 -1
- package/web-app/dist/assets/{HomePage-pU_0fGny.js → HomePage-BQk-MUjn.js} +4 -4
- package/web-app/dist/assets/{LoginPage-DTZtt2Yb.js → LoginPage-DMOZVGGL.js} +1 -1
- package/web-app/dist/assets/{MagicPage-10zfra8o.js → MagicPage-Bzp2Nt1z.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C-wiKUkv.js → MetricsPage-C39JVdsw.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BDkcmhYe.js → NotFoundPage-6vT_U9UL.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-CiCavQ8n.js → ProjectPage-BfFcZp-E.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BLCXQwwC.js → ProjectsPage-CPMBf8Wt.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-PkxtaMyg.js → SettingsPage-BnNN6ETl.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-iECp8Tha.js → ShowcasePage-WDrMf-cx.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DS6Anno1.js → SystemSettingsPage-DX4jb2e8.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-ls6h6bNL.js → TeamsPage-BCfqcXzu.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-Bk0QzlPt.js → TemplatesPage-CZvmimDj.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-4-1hWCtZ.js → TerminalOutput-BlRqFwWV.js} +1 -1
- package/web-app/dist/assets/{activity-DH3ih2nS.js → activity-CacZsUyr.js} +1 -1
- package/web-app/dist/assets/{bell-Gn17S6uv.js → bell-DK2qtHnk.js} +1 -1
- package/web-app/dist/assets/{bot-Cbycc3VE.js → bot-CkcUtHad.js} +1 -1
- package/web-app/dist/assets/{check-nIAqa-kf.js → check-CbCPjX3M.js} +1 -1
- package/web-app/dist/assets/{chevron-left-D2jcWDll.js → chevron-left-5NUKWw3i.js} +1 -1
- package/web-app/dist/assets/{circle-alert-CpL4Bhvt.js → circle-alert-S7uFoxC2.js} +1 -1
- package/web-app/dist/assets/{clock-IW4Wq86N.js → clock-CaQRrIrs.js} +1 -1
- package/web-app/dist/assets/{cloud-Cn8nNuH2.js → cloud-DBAX6c0r.js} +1 -1
- package/web-app/dist/assets/{code-xml-BiJBteXf.js → code-xml-De5-EXv3.js} +1 -1
- package/web-app/dist/assets/{copy-CnqkyNsi.js → copy-CUkT6k1v.js} +1 -1
- package/web-app/dist/assets/{database-CKSReqa5.js → database-BAWf1Gwt.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-CDzDY64R.js → dollar-sign-Ji8zk86R.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-Box4IwG1.js → file-code-corner-ChtXoBwS.js} +1 -1
- package/web-app/dist/assets/{file-plus-DpGqlXF8.js → file-plus-bFa37P76.js} +1 -1
- package/web-app/dist/assets/{folder-open-B57dAoBv.js → folder-open-DhXpXscO.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-BVbucmO5.js → git-commit-horizontal-DVPeDQ3j.js} +1 -1
- package/web-app/dist/assets/{globe-BkOnKl4x.js → globe-BPZgPeeu.js} +1 -1
- package/web-app/dist/assets/{hammer-DRbIQ4QU.js → hammer-jLCaujYH.js} +1 -1
- package/web-app/dist/assets/{index-CM_b_EhP.js → index-B-0iHBPO.js} +2 -2
- package/web-app/dist/assets/{layers-B78BiFiU.js → layers-B1vsrsFW.js} +1 -1
- package/web-app/dist/assets/{lightbulb-B-Itbm9g.js → lightbulb-C-uLoq9Y.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Oq6NQhW2.js → loader-circle-JTfD-ZuM.js} +1 -1
- package/web-app/dist/assets/{lock-DbJ9zxbw.js → lock-G9rxD4gZ.js} +1 -1
- package/web-app/dist/assets/{mail-CzMRod6m.js → mail-BJ0PTN_V.js} +1 -1
- package/web-app/dist/assets/{package-WZ5osvej.js → package-CXClfLOO.js} +1 -1
- package/web-app/dist/assets/{plus-j08lFR-K.js → plus-EoL5OCB7.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-CIr7E-g2.js → refresh-cw-BjREUnVq.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-gwoXxDeE.js → rotate-ccw-DahWX07H.js} +1 -1
- package/web-app/dist/assets/{save-B8fV_ZpE.js → save-Dek3gCn1.js} +1 -1
- package/web-app/dist/assets/{server-D5dO1paz.js → server-D6V1BAia.js} +1 -1
- package/web-app/dist/assets/{shield-alert-Du08zhdg.js → shield-alert-BtTK5Sxb.js} +1 -1
- package/web-app/dist/assets/{trash-2-DEKSVae5.js → trash-2-BT5o_g0r.js} +1 -1
- package/web-app/dist/assets/{trending-down-DBiXUtxJ.js → trending-down-D4Jk7KF3.js} +1 -1
- package/web-app/dist/assets/{trending-up-BgmK_tHq.js → trending-up-EQFTzhEo.js} +1 -1
- package/web-app/dist/assets/{upload-IaViyeVD.js → upload-JfI5lCSE.js} +1 -1
- package/web-app/dist/assets/{usePolling-PiRLqNu6.js → usePolling-BnhPUuGd.js} +1 -1
- package/web-app/dist/assets/{user-BB5J8wAF.js → user-DSUiUYtj.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/web-app/dist/assets/ComparePage-Dg0UdZAk.js +0 -1
package/dashboard/auth.py
CHANGED
|
@@ -477,6 +477,111 @@ def _base64url_decode(data: str) -> bytes:
|
|
|
477
477
|
return base64.urlsafe_b64decode(data)
|
|
478
478
|
|
|
479
479
|
|
|
480
|
+
# Role precedence (highest privilege first). When a token carries multiple
|
|
481
|
+
# recognized role claims, the highest-privilege match wins.
|
|
482
|
+
_ROLE_PRECEDENCE = ("admin", "operator", "auditor", "viewer")
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _normalize_claim_values(value) -> set[str]:
|
|
486
|
+
"""Normalize an OIDC claim value into a lowercased set of strings.
|
|
487
|
+
|
|
488
|
+
Claim values may be a single string, a space-separated string, or a
|
|
489
|
+
list of strings (different providers use different shapes). All are
|
|
490
|
+
flattened into a set of lowercased tokens for matching against ROLES.
|
|
491
|
+
"""
|
|
492
|
+
out: set[str] = set()
|
|
493
|
+
if value is None:
|
|
494
|
+
return out
|
|
495
|
+
if isinstance(value, str):
|
|
496
|
+
for part in value.split():
|
|
497
|
+
if part:
|
|
498
|
+
out.add(part.strip().lower())
|
|
499
|
+
elif isinstance(value, (list, tuple, set)):
|
|
500
|
+
for item in value:
|
|
501
|
+
if isinstance(item, str):
|
|
502
|
+
s = item.strip().lower()
|
|
503
|
+
if s:
|
|
504
|
+
out.add(s)
|
|
505
|
+
return out
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _collect_role_claims(claims: dict) -> set[str]:
|
|
509
|
+
"""Collect candidate role/group values from standard OIDC claim shapes.
|
|
510
|
+
|
|
511
|
+
Recognized sources (case-insensitive values flattened into one set):
|
|
512
|
+
- A configurable claim named by LOKI_OIDC_ROLES_CLAIM (supports a dotted
|
|
513
|
+
path for nested claims, e.g. "realm_access.roles" for Keycloak).
|
|
514
|
+
- "roles" (generic)
|
|
515
|
+
- "groups" (generic)
|
|
516
|
+
- "realm_access.roles" (Keycloak)
|
|
517
|
+
- "cognito:groups" (AWS Cognito)
|
|
518
|
+
|
|
519
|
+
Note: "groups"/"cognito:groups" typically carry arbitrary group names,
|
|
520
|
+
not Loki role names. Only values that exactly match one of the four
|
|
521
|
+
built-in role names (admin/operator/viewer/auditor, case-insensitive)
|
|
522
|
+
grant a role. Everything else is ignored and the default role applies.
|
|
523
|
+
"""
|
|
524
|
+
candidates: set[str] = set()
|
|
525
|
+
|
|
526
|
+
def _read_dotted(path: str):
|
|
527
|
+
node = claims
|
|
528
|
+
for key in path.split("."):
|
|
529
|
+
if isinstance(node, dict) and key in node:
|
|
530
|
+
node = node[key]
|
|
531
|
+
else:
|
|
532
|
+
return None
|
|
533
|
+
return node
|
|
534
|
+
|
|
535
|
+
configured = os.environ.get("LOKI_OIDC_ROLES_CLAIM", "").strip()
|
|
536
|
+
sources = []
|
|
537
|
+
if configured:
|
|
538
|
+
sources.append(configured)
|
|
539
|
+
sources.extend(["roles", "groups", "realm_access.roles", "cognito:groups"])
|
|
540
|
+
|
|
541
|
+
for src in sources:
|
|
542
|
+
if "." in src:
|
|
543
|
+
val = _read_dotted(src)
|
|
544
|
+
else:
|
|
545
|
+
val = claims.get(src)
|
|
546
|
+
candidates |= _normalize_claim_values(val)
|
|
547
|
+
|
|
548
|
+
return candidates
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _default_oidc_role() -> str:
|
|
552
|
+
"""Return the configured default OIDC role, validated against ROLES.
|
|
553
|
+
|
|
554
|
+
Defaults to the least-privileged role ("viewer"). If LOKI_OIDC_DEFAULT_ROLE
|
|
555
|
+
is set to an unrecognized value, falls back to "viewer" (never admin).
|
|
556
|
+
"""
|
|
557
|
+
configured = os.environ.get("LOKI_OIDC_DEFAULT_ROLE", "").strip().lower()
|
|
558
|
+
if configured in ROLES:
|
|
559
|
+
return configured
|
|
560
|
+
return "viewer"
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _scopes_from_claims(claims: dict) -> tuple[list[str], str]:
|
|
564
|
+
"""Map OIDC token claims to Loki scopes via the existing ROLES mapping.
|
|
565
|
+
|
|
566
|
+
Returns a tuple of (scopes, role_name). If no recognized role claim is
|
|
567
|
+
present, the safe default role (viewer, or LOKI_OIDC_DEFAULT_ROLE) is
|
|
568
|
+
applied. This function NEVER returns ["*"]/admin by default: full access
|
|
569
|
+
is granted only when an explicit admin role claim is present.
|
|
570
|
+
"""
|
|
571
|
+
candidate_values = _collect_role_claims(claims)
|
|
572
|
+
|
|
573
|
+
matched_role = None
|
|
574
|
+
for role in _ROLE_PRECEDENCE:
|
|
575
|
+
if role in candidate_values:
|
|
576
|
+
matched_role = role
|
|
577
|
+
break
|
|
578
|
+
|
|
579
|
+
if matched_role is None:
|
|
580
|
+
matched_role = _default_oidc_role()
|
|
581
|
+
|
|
582
|
+
return resolve_scopes(matched_role), matched_role
|
|
583
|
+
|
|
584
|
+
|
|
480
585
|
def validate_oidc_token(token_str: str) -> Optional[dict]:
|
|
481
586
|
"""Validate an OIDC JWT token.
|
|
482
587
|
|
|
@@ -489,6 +594,12 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
|
|
|
489
594
|
- Audience matches OIDC_AUDIENCE or OIDC_CLIENT_ID
|
|
490
595
|
- Token is not expired
|
|
491
596
|
|
|
597
|
+
On success, role/group claims are mapped to Loki roles (admin/operator/
|
|
598
|
+
viewer/auditor) via _scopes_from_claims. When no recognized role claim is
|
|
599
|
+
present, the least-privileged default role (viewer, configurable via
|
|
600
|
+
LOKI_OIDC_DEFAULT_ROLE) is applied. OIDC users are never granted ["*"]
|
|
601
|
+
unless an explicit admin role claim is present.
|
|
602
|
+
|
|
492
603
|
SECURITY CRITICAL: Without PyJWT, JWT signatures are NOT cryptographically
|
|
493
604
|
verified. An attacker can forge tokens with arbitrary claims. For any
|
|
494
605
|
production deployment, you MUST install PyJWT + cryptography so that
|
|
@@ -529,11 +640,13 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
|
|
|
529
640
|
issuer=OIDC_ISSUER,
|
|
530
641
|
)
|
|
531
642
|
|
|
643
|
+
scopes, role = _scopes_from_claims(decoded)
|
|
532
644
|
return {
|
|
533
645
|
"id": decoded.get("sub", ""),
|
|
534
646
|
"name": decoded.get("name", decoded.get("email", decoded.get("sub", ""))),
|
|
535
647
|
"email": decoded.get("email", ""),
|
|
536
|
-
"scopes":
|
|
648
|
+
"scopes": scopes, # mapped from OIDC role/group claims
|
|
649
|
+
"role": role,
|
|
537
650
|
"auth_method": "oidc",
|
|
538
651
|
"issuer": decoded.get("iss"),
|
|
539
652
|
}
|
|
@@ -602,11 +715,13 @@ def validate_oidc_token(token_str: str) -> Optional[dict]:
|
|
|
602
715
|
return None
|
|
603
716
|
|
|
604
717
|
# Return user info from claims
|
|
718
|
+
scopes, role = _scopes_from_claims(claims)
|
|
605
719
|
return {
|
|
606
720
|
"id": claims.get("sub", ""),
|
|
607
721
|
"name": claims.get("name", claims.get("email", claims.get("sub", ""))),
|
|
608
722
|
"email": claims.get("email", ""),
|
|
609
|
-
"scopes":
|
|
723
|
+
"scopes": scopes, # mapped from OIDC role/group claims
|
|
724
|
+
"role": role,
|
|
610
725
|
"auth_method": "oidc",
|
|
611
726
|
"issuer": claims.get("iss"),
|
|
612
727
|
}
|
package/dashboard/telemetry.py
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"""Anonymous usage telemetry for Loki Mode dashboard.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Collection is OPT-IN and OFF by default. Nothing is sent unless the user
|
|
4
|
+
explicitly opts in, so a default install (including air-gapped, GDPR, and
|
|
5
|
+
FedRAMP deployments) never phones home.
|
|
6
|
+
|
|
7
|
+
Opt-in (one required): LOKI_TELEMETRY=on OR ~/.loki/config: TELEMETRY_ENABLED=true
|
|
8
|
+
Opt-out (always wins): LOKI_TELEMETRY=off / LOKI_TELEMETRY_DISABLED=true /
|
|
9
|
+
DO_NOT_TRACK=1 / ~/.loki/config: TELEMETRY_DISABLED=true
|
|
10
|
+
|
|
4
11
|
All calls are fire-and-forget, silent on failure, non-blocking.
|
|
5
12
|
"""
|
|
6
13
|
|
|
@@ -19,10 +26,20 @@ _POSTHOG_KEY = "phc_ya0vGBru41AJWtGNfZZ8H9W4yjoZy4KON0nnayS7s87"
|
|
|
19
26
|
|
|
20
27
|
|
|
21
28
|
def _is_enabled():
|
|
22
|
-
# Unified
|
|
23
|
-
#
|
|
24
|
-
# crash
|
|
25
|
-
|
|
29
|
+
# Unified OPT-IN gate. Collection is OFF by default; enabled ONLY when the
|
|
30
|
+
# user has opted in AND has not also opted out. This precedence MUST mirror
|
|
31
|
+
# loki_collection_enabled in autonomy/crash.sh and _loki_telemetry_enabled
|
|
32
|
+
# in autonomy/telemetry.sh so one model gates BOTH PostHog usage telemetry
|
|
33
|
+
# and crash reporting.
|
|
34
|
+
#
|
|
35
|
+
# Precedence:
|
|
36
|
+
# 1. Any opt-out flag present -> False (hard kill, always wins)
|
|
37
|
+
# 2. Else any opt-in flag present -> True
|
|
38
|
+
# 3. Else (default) -> False (no egress)
|
|
39
|
+
telem = os.environ.get("LOKI_TELEMETRY", "").lower()
|
|
40
|
+
|
|
41
|
+
# --- 1. Opt-out always wins ---
|
|
42
|
+
if telem == "off":
|
|
26
43
|
return False
|
|
27
44
|
if os.environ.get("LOKI_TELEMETRY_DISABLED") == "true":
|
|
28
45
|
return False
|
|
@@ -30,15 +47,26 @@ def _is_enabled():
|
|
|
30
47
|
return False
|
|
31
48
|
# Persistent opt-out in ~/.loki/config (matches the bash grep prefix
|
|
32
49
|
# semantics: any line beginning with TELEMETRY_DISABLED=true).
|
|
50
|
+
config_enabled = False
|
|
33
51
|
try:
|
|
34
52
|
config_path = Path.home() / ".loki" / "config"
|
|
35
53
|
if config_path.is_file():
|
|
36
54
|
for line in config_path.read_text().splitlines():
|
|
37
55
|
if line.startswith("TELEMETRY_DISABLED=true"):
|
|
38
56
|
return False
|
|
57
|
+
if line.startswith("TELEMETRY_ENABLED=true"):
|
|
58
|
+
config_enabled = True
|
|
39
59
|
except Exception:
|
|
40
60
|
pass
|
|
41
|
-
|
|
61
|
+
|
|
62
|
+
# --- 2. Opt-in required to enable ---
|
|
63
|
+
if telem == "on":
|
|
64
|
+
return True
|
|
65
|
+
if config_enabled:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# --- 3. Default: OFF ---
|
|
69
|
+
return False
|
|
42
70
|
|
|
43
71
|
|
|
44
72
|
def _get_distinct_id():
|
package/docs/ACKNOWLEDGEMENTS.md
CHANGED
|
@@ -336,7 +336,7 @@ Based on research synthesis, the following improvements are planned:
|
|
|
336
336
|
|
|
337
337
|
This acknowledgements file documents the research and resources that influenced Loki Mode's design. All referenced works retain their original licenses and copyrights.
|
|
338
338
|
|
|
339
|
-
Loki Mode itself is released under the
|
|
339
|
+
Loki Mode itself is released under the Business Source License 1.1 (BUSL-1.1), a source-available license.
|
|
340
340
|
|
|
341
341
|
---
|
|
342
342
|
|
|
@@ -45,7 +45,7 @@ GSD is the closest competitor -- a context engineering system that spawns fresh
|
|
|
45
45
|
| **Enterprise Security** | `--dangerously-skip-permissions` | MCP sandboxed | Sandboxed | Audit logs, RBAC | Staged autonomy | Sandboxed |
|
|
46
46
|
| **Cross-Project Learning** | No | AgentDB | No | No | No | Limited |
|
|
47
47
|
| **Observability** | Dashboard + STATUS.txt | Real-time tracing | Logs | Full tracing | Built-in | Full |
|
|
48
|
-
| **Pricing** | Free (
|
|
48
|
+
| **Pricing** | Free (source-available) | Free (OSS) | Free (OSS) | $25+/mo | $20-400/mo | $20-500/mo |
|
|
49
49
|
| **Production Ready** | Experimental | Production | Production | Production | Production | Production |
|
|
50
50
|
| **Resource Monitoring** | Yes (v2.18.5) | Unknown | No | No | No | No |
|
|
51
51
|
| **State Recovery** | Yes (checkpoints) | Yes (AgentDB) | Limited | Yes | Git worktrees | Yes |
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.48.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -114,11 +114,18 @@ faster routed commands and forward-compat with v8.0.0.
|
|
|
114
114
|
- Installs the `loki` CLI binary to your PATH (`bin/loki` shim)
|
|
115
115
|
- Subsequent `loki setup-skill` creates symlinks at `~/.claude/skills/loki-mode`, `~/.codex/skills/loki-mode`
|
|
116
116
|
|
|
117
|
-
**
|
|
117
|
+
**Anonymous telemetry is OPT-IN and OFF by default.** A default `npm install`
|
|
118
|
+
sends nothing, so air-gapped and enterprise installs are safe out of the box. To
|
|
119
|
+
opt in to anonymous diagnostics, run `loki telemetry on` or set
|
|
120
|
+
`LOKI_TELEMETRY=on`. To make opting in impossible across a fleet, bake an
|
|
121
|
+
opt-out into your base image (opt-out always wins):
|
|
118
122
|
```bash
|
|
123
|
+
# Hard-disable everywhere (belt and suspenders; opt-out always wins):
|
|
119
124
|
LOKI_TELEMETRY_DISABLED=true npm install -g loki-mode
|
|
120
125
|
# Or set DO_NOT_TRACK=1
|
|
121
126
|
```
|
|
127
|
+
See [PRIVACY.md](./PRIVACY.md) for the exact data sent and the full opt-in /
|
|
128
|
+
opt-out model.
|
|
122
129
|
|
|
123
130
|
**Update:** `npm update -g loki-mode`
|
|
124
131
|
|
|
@@ -389,7 +396,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
|
|
|
389
396
|
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
390
397
|
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
391
398
|
-v $(pwd):/workspace -w /workspace \
|
|
392
|
-
asklokesh/loki-mode:7.
|
|
399
|
+
asklokesh/loki-mode:7.48.0 start ./my-spec.md
|
|
393
400
|
```
|
|
394
401
|
|
|
395
402
|
##### docker compose + .env (no host install)
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# Loki Mode open-core boundary
|
|
2
2
|
|
|
3
|
-
Loki Mode is and stays
|
|
4
|
-
|
|
3
|
+
Loki Mode is and stays source-available (BUSL-1.1) and free to self-host. This
|
|
4
|
+
document draws the line between what is free forever and what
|
|
5
|
+
hosted/paid/enterprise plans would add on top. R9 ships
|
|
5
6
|
the SEAMS for that line; it does not ship a hosted backend, a license server, or
|
|
6
7
|
any paywall on existing functionality.
|
|
7
8
|
|
|
8
9
|
## Principle
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
today runs locally, free, with no account, no license key,
|
|
12
|
-
to any Loki service. Hosted/paid features are ADDITIVE convenience and
|
|
11
|
+
The free self-hosted tier is fully functional with zero hosted backend. Every
|
|
12
|
+
capability Loki has today runs locally, free, with no account, no license key,
|
|
13
|
+
and no network call to any Loki service. Hosted/paid features are ADDITIVE convenience and
|
|
13
14
|
team/enterprise layers, never a removal or gating of something that is free
|
|
14
15
|
today.
|
|
15
16
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# P2 Spec Robustness Plan (P2-1 spec interrogation gate + P2-2 assumption ledger)
|
|
2
|
+
|
|
3
|
+
Status: design for implementation. No version bump, no commit in this arc.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Loki must stay accurate even when the input spec is WRONG, ambiguous, or
|
|
8
|
+
incomplete. Today two building blocks already detect spec defects but neither
|
|
9
|
+
feeds the autonomous loop:
|
|
10
|
+
|
|
11
|
+
- `autonomy/grill.sh` invokes the provider once with a Devil's-Advocate prompt
|
|
12
|
+
and writes 10-15 hardest spec questions to `.loki/grill/report.md`. It is
|
|
13
|
+
CLI-only (`grep grill autonomy/run.sh` = 0 invocations) and nothing reads its
|
|
14
|
+
output.
|
|
15
|
+
- `autonomy/prd-analyzer.py` detects missing PRD dimensions and has a
|
|
16
|
+
deterministic `_make_assumption()` map, writing `.loki/prd-observations.md`,
|
|
17
|
+
which nothing reads. Its interactive Q&A is inert in non-TTY (autonomous) runs.
|
|
18
|
+
|
|
19
|
+
The fix: run interrogation automatically in DISCOVERY, classify the findings,
|
|
20
|
+
record every spec gap as a first-class ASSUMPTION in a tracked ledger, BLOCK
|
|
21
|
+
completion while high-severity assumptions are unconfirmed-and-unacknowledged,
|
|
22
|
+
and surface the ledger in the proof-of-done output. Defects are SURFACED as
|
|
23
|
+
recorded assumptions, never silently autocorrected.
|
|
24
|
+
|
|
25
|
+
## Core design decision: auto-acknowledgment lifecycle (prevents the trap)
|
|
26
|
+
|
|
27
|
+
A naive "block completion while any high-severity assumption is unconfirmed"
|
|
28
|
+
hard-blocks EVERY ambiguous run to max-iterations, because in autonomous
|
|
29
|
+
(non-TTY) mode no human can ever set `confirmed=yes`. We never reach the
|
|
30
|
+
"done, plus here is what I assumed" output the goal demands.
|
|
31
|
+
|
|
32
|
+
Resolution: split the gate from the lifecycle.
|
|
33
|
+
|
|
34
|
+
- The gate `council_assumption_ledger_gate` is a PURE function of ledger state.
|
|
35
|
+
It blocks iff an entry has `severity=high AND confirmed=false AND
|
|
36
|
+
acknowledged=false`.
|
|
37
|
+
- The auto-acknowledgment lifecycle lives in run.sh (NOT in the gate). Once an
|
|
38
|
+
assumption has been written into the ledger AND injected into the build prompt
|
|
39
|
+
at least once, run.sh marks it `acknowledged=true`. Default-on; opt-out
|
|
40
|
+
`LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1` keeps a human-in-the-loop path where only
|
|
41
|
+
`confirmed=true` clears the block.
|
|
42
|
+
|
|
43
|
+
This is the OPPOSITE of silent autocorrect: the assumption is recorded,
|
|
44
|
+
injected into the agent's prompt, and surfaced in proof-of-done. Acknowledgment
|
|
45
|
+
records "Loki has SEEN this gap and proceeded with a stated default", not "Loki
|
|
46
|
+
hid it". The gate still has teeth on the first iteration (high-sev unacknowledged
|
|
47
|
+
blocks) and in the require-confirm path.
|
|
48
|
+
|
|
49
|
+
## Severity rule (deterministic, no LLM)
|
|
50
|
+
|
|
51
|
+
grill emits no severity. Classify by section / keyword on the read side:
|
|
52
|
+
|
|
53
|
+
- HIGH: security blind spots; scale/reliability blind spots; missing or
|
|
54
|
+
untestable acceptance criteria; any line containing contradiction keywords
|
|
55
|
+
(contradict, conflict, inconsistent, mutually exclusive).
|
|
56
|
+
- MEDIUM: ambiguities; unstated assumptions; underspecified behavior; all
|
|
57
|
+
prd-analyzer missing-dimension assumptions.
|
|
58
|
+
|
|
59
|
+
This guarantees a HIGH tier exists (so the gate has teeth) and is fully
|
|
60
|
+
deterministic (so tests are reproducible).
|
|
61
|
+
|
|
62
|
+
## Taxonomy mapping (classification, read-side only)
|
|
63
|
+
|
|
64
|
+
grill section -> finding class:
|
|
65
|
+
- "Ambiguities and missing acceptance criteria" -> ambiguous (HIGH if the line
|
|
66
|
+
references acceptance criteria / testable / measurable; else MEDIUM)
|
|
67
|
+
- "Unstated assumptions" -> underspecified (MEDIUM)
|
|
68
|
+
- "Security blind spots" -> missing (HIGH)
|
|
69
|
+
- "Scale and reliability blind spots" -> missing (HIGH)
|
|
70
|
+
- any line with a contradiction keyword (any section) -> contradictory (HIGH)
|
|
71
|
+
- prd-analyzer missing dimensions -> missing (MEDIUM, deterministic default)
|
|
72
|
+
|
|
73
|
+
"None identified." lines are skipped (no fabricated findings).
|
|
74
|
+
|
|
75
|
+
grill output contract is NOT changed (it is parsed by the loki-grill skill).
|
|
76
|
+
We classify a COPY of its markdown; grill.sh stays byte-identical.
|
|
77
|
+
|
|
78
|
+
## No-fabrication rule for ledger content
|
|
79
|
+
|
|
80
|
+
A grill finding is a QUESTION, not a resolution. The ledger `assumption` field
|
|
81
|
+
for a grill-derived gap is an honest "spec gives no answer; proceeding with the
|
|
82
|
+
implementer default for <area>" plus `affects=<area>`. We do NOT invent a
|
|
83
|
+
specific resolution the build will not actually follow. prd-analyzer assumptions
|
|
84
|
+
reuse its existing deterministic `_make_assumption()` text verbatim.
|
|
85
|
+
|
|
86
|
+
## Ledger schema (`.loki/assumptions/`)
|
|
87
|
+
|
|
88
|
+
One JSON file per assumption: `.loki/assumptions/<id>.json`, plus a
|
|
89
|
+
human-readable `.loki/assumptions/ledger.md` rollup regenerated on each write.
|
|
90
|
+
Each entry:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"id": "a-0001",
|
|
95
|
+
"gap": "<the spec defect / unanswered question, verbatim>",
|
|
96
|
+
"assumption": "<honest stated default Loki proceeds with>",
|
|
97
|
+
"why": "<why this assumption / where the gap came from: grill|prd-analyzer>",
|
|
98
|
+
"severity": "high|medium",
|
|
99
|
+
"class": "ambiguous|contradictory|underspecified|missing",
|
|
100
|
+
"affects": "<area, e.g. security, acceptance-criteria, data-model>",
|
|
101
|
+
"source": "grill|prd-analyzer",
|
|
102
|
+
"confirmed": false,
|
|
103
|
+
"acknowledged": false,
|
|
104
|
+
"created_at": "<iso8601>"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Stable id = `a-` + zero-padded counter over existing files (idempotent: a second
|
|
109
|
+
DISCOVERY run with the same findings does not duplicate; dedupe on the `gap`
|
|
110
|
+
text hash).
|
|
111
|
+
|
|
112
|
+
## Build surface (files + functions)
|
|
113
|
+
|
|
114
|
+
1. NEW `autonomy/spec-interrogation.sh` (sourced by run.sh; standalone-testable):
|
|
115
|
+
- `spec_interrogation_classify_report <report.md path>`: pure classifier.
|
|
116
|
+
Reads grill markdown, emits one TSV/JSON finding per question line with
|
|
117
|
+
class + severity. Takes a file so a fixture report drives the test with no
|
|
118
|
+
`claude` call.
|
|
119
|
+
- `spec_interrogation_severity_for <section> <line>`: deterministic severity.
|
|
120
|
+
- `spec_ledger_write <gap> <assumption> <why> <severity> <class> <affects>
|
|
121
|
+
<source>`: idempotent writer (dedupe on gap hash) -> `.loki/assumptions/`.
|
|
122
|
+
- `spec_ledger_rebuild_md`: regenerate `.loki/assumptions/ledger.md`.
|
|
123
|
+
- `spec_ledger_high_unresolved_count`: count entries with
|
|
124
|
+
`severity=high AND confirmed=false AND acknowledged=false` (gate input;
|
|
125
|
+
also reused by the summary).
|
|
126
|
+
- `spec_ledger_acknowledge_all`: set `acknowledged=true` on all entries
|
|
127
|
+
(auto-ack lifecycle helper; default path).
|
|
128
|
+
- `spec_interrogation_run <spec_path>`: orchestrator. Default-on
|
|
129
|
+
(`LOKI_SPEC_GRILL=0` opts out). Provider-aware: source grill.sh, call
|
|
130
|
+
`grill_check_provider`; if provider absent, log honest message, skip the
|
|
131
|
+
grill subcall (NO fabricated questions), but STILL fold prd-analyzer
|
|
132
|
+
missing-dimension assumptions into the ledger so degrade surfaces something
|
|
133
|
+
non-blocking. On provider present: run `grill_main` (writes report.md),
|
|
134
|
+
classify it, write ledger entries. Always non-fatal to the run.
|
|
135
|
+
|
|
136
|
+
2. `autonomy/run.sh` DISCOVERY (~13056, after prd-analyzer + council_init,
|
|
137
|
+
before the iteration loop): source spec-interrogation.sh and call
|
|
138
|
+
`spec_interrogation_run "$prd_path"`. This is the grep-able grill invocation
|
|
139
|
+
the task requires. Best-effort (`|| true`), never blocks startup.
|
|
140
|
+
|
|
141
|
+
3. `autonomy/run.sh` auto-ack lifecycle: after the build prompt is constructed
|
|
142
|
+
each iteration (assumptions are injected into the prompt via build_prompt),
|
|
143
|
+
call `spec_ledger_acknowledge_all` UNLESS
|
|
144
|
+
`LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1`. Inject the high-severity assumption
|
|
145
|
+
list into the build prompt (so the agent sees the gaps it must respect).
|
|
146
|
+
|
|
147
|
+
4. `autonomy/completion-council.sh` `council_assumption_ledger_gate` (new),
|
|
148
|
+
slotted into `council_evaluate` right after `council_evidence_gate`
|
|
149
|
+
(mirrors 2510-2513). Same defensive `COUNCIL_STATE_DIR` default, opt-out
|
|
150
|
+
`LOKI_ASSUMPTION_GATE=0`. Blocks iff `spec_ledger_high_unresolved_count > 0`.
|
|
151
|
+
Writes `.loki/council/assumption-block.json` on block, removes it on pass.
|
|
152
|
+
Also wired into the completion-promise route in run.sh (~14525 pattern) and
|
|
153
|
+
the code_review gate chain (~15013) so the promise path cannot bypass it.
|
|
154
|
+
|
|
155
|
+
5. `autonomy/run.sh` `build_completion_summary` (~2637): emit an
|
|
156
|
+
"Assumptions recorded: N (M high-severity)" block into COMPLETION.txt and the
|
|
157
|
+
ledger list, plus the count into completion.json. So "done" means "done, plus
|
|
158
|
+
here are the N places your spec was ambiguous and what I assumed."
|
|
159
|
+
|
|
160
|
+
6. NEW `tests/test-spec-interrogation.sh` (bash convention, ok/bad counters,
|
|
161
|
+
source the module, mktemp fixtures):
|
|
162
|
+
- (a) classifier on a fixture grill report writes classified findings to the
|
|
163
|
+
ledger (ambiguous/contradictory/underspecified/missing + high/medium).
|
|
164
|
+
- (b) a ledger with one high/confirmed:false/acknowledged:false entry makes
|
|
165
|
+
`council_assumption_ledger_gate` return 1 (BLOCK) and write
|
|
166
|
+
assumption-block.json.
|
|
167
|
+
- (c) clean spec (no high-sev entries, or all acknowledged) -> gate returns 0
|
|
168
|
+
(no spurious block), no block file.
|
|
169
|
+
- (d) no provider -> `spec_interrogation_run` degrades cleanly: honest
|
|
170
|
+
message, prd-analyzer assumptions still folded (medium, non-blocking), run
|
|
171
|
+
proceeds, gate passes.
|
|
172
|
+
|
|
173
|
+
## Gate reachability (resolved open question)
|
|
174
|
+
|
|
175
|
+
The existing gates fire from THREE sites: `council_evaluate` (~2510), the
|
|
176
|
+
completion-promise route (~14525), and the code_review gate chain (~15013). The
|
|
177
|
+
new gate is wired into all three so high-sev unacknowledged assumptions cannot
|
|
178
|
+
slip through the promise path.
|
|
179
|
+
|
|
180
|
+
## Opt-out knobs (all default-on, intelligent)
|
|
181
|
+
|
|
182
|
+
- `LOKI_SPEC_GRILL=0` -> skip interrogation entirely.
|
|
183
|
+
- `LOKI_ASSUMPTION_GATE=0` -> gate is pass-through.
|
|
184
|
+
- `LOKI_ASSUMPTIONS_REQUIRE_CONFIRM=1` -> require human `confirmed=true`
|
|
185
|
+
(disables auto-ack); the human-in-the-loop path.
|
|
186
|
+
|
|
187
|
+
No "user must decide the type" knob. Classification + severity are automatic.
|
|
188
|
+
|
|
189
|
+
## Constraints
|
|
190
|
+
|
|
191
|
+
No emojis, no em dashes, no version bump, no commit, no push. Provider-aware,
|
|
192
|
+
degrade cleanly, no fabricated questions when provider absent.
|
package/docs/PRIVACY.md
CHANGED
|
@@ -6,14 +6,21 @@ match the code, the code is the bug; please open an issue.
|
|
|
6
6
|
|
|
7
7
|
## Summary
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
- Anonymous diagnostics are OPT-IN and OFF by default. A default install sends
|
|
10
|
+
no telemetry or diagnostics of any kind. This covers a default `npm install`,
|
|
11
|
+
the CLI (session and command events), the dashboard, and the welcome page
|
|
12
|
+
form. Air-gapped, GDPR, and FedRAMP deployments are safe out of the box: an
|
|
13
|
+
untouched install sends us no telemetry or diagnostics. (This statement scopes
|
|
14
|
+
to telemetry and diagnostics; provider CLIs you configure, such as Claude or
|
|
15
|
+
Codex, make their own network calls under your own credentials and are
|
|
16
|
+
governed by their vendors.)
|
|
17
|
+
- When you DO opt in, Loki Mode collects anonymous diagnostics to help find and
|
|
18
|
+
fix bugs. It NEVER collects your code, prompts, PRDs, file paths, environment
|
|
19
|
+
values, API keys, repository names, emails, or IP addresses.
|
|
20
|
+
- Crash reporting (Phase 0) is local-only with zero network egress regardless,
|
|
21
|
+
and is also gated by opt-in, so a default install writes nothing at all.
|
|
22
|
+
- You opt in with a single switch (`loki telemetry on` or `LOKI_TELEMETRY=on`)
|
|
23
|
+
and can opt back out at any time. Opt-out always wins over opt-in.
|
|
17
24
|
|
|
18
25
|
## Two collection paths exist
|
|
19
26
|
|
|
@@ -39,17 +46,35 @@ Phase 0 behavior:
|
|
|
39
46
|
GitHub issue URL so you can submit it manually if you choose. Loki Mode does
|
|
40
47
|
not submit anything for you in this version.
|
|
41
48
|
|
|
42
|
-
### 2. Usage telemetry (
|
|
49
|
+
### 2. Usage telemetry (anonymous, opt-in)
|
|
43
50
|
|
|
44
|
-
Loki Mode
|
|
45
|
-
|
|
51
|
+
Loki Mode can send anonymous usage telemetry via PostHog, but ONLY after you opt
|
|
52
|
+
in. By default it is OFF and nothing is sent.
|
|
46
53
|
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
-
|
|
54
|
+
- Endpoint: `https://us.i.posthog.com/capture/` (override with
|
|
55
|
+
`LOKI_TELEMETRY_ENDPOINT`). The PostHog project key is a public ingest key.
|
|
56
|
+
- Events: `install` (on `npm install`), `session_start`, `session_end`,
|
|
57
|
+
`cli_command`, and `dashboard_start`.
|
|
58
|
+
- Exact payload (every event): `os` (uname system), `arch` (CPU arch),
|
|
59
|
+
`version` (Loki Mode version), `channel` (npm / docker / homebrew / skill /
|
|
60
|
+
source), and a random per-machine `distinct_id` (a uuid4 stored in
|
|
61
|
+
`~/.loki-telemetry-id`, never an email or name). The `install` event also adds
|
|
62
|
+
`node_version` and `providers_installed` (which provider CLIs were detected,
|
|
63
|
+
e.g. "claude,codex"). Some events add a small free-of-PII property such as the
|
|
64
|
+
command name. No code, prompts, paths, keys, repo names, emails, or IPs.
|
|
50
65
|
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
### 3. Welcome page form (anonymous, explicit submit, opt-in)
|
|
67
|
+
|
|
68
|
+
The `loki welcome` page (`assets/welcome/welcome.html`) shows an optional form.
|
|
69
|
+
It NEVER sends anything on page load and is rendered inert unless you have opted
|
|
70
|
+
in. If you have opted in AND you choose to fill in and submit the form, it sends
|
|
71
|
+
these additional self-reported fields to PostHog: your role, company size, and
|
|
72
|
+
the tools you use, plus the same anonymous `distinct_id`. It still never sends
|
|
73
|
+
your name, email, or IP. In headless / Docker / CI environments there is no
|
|
74
|
+
browser, so this path never runs.
|
|
75
|
+
|
|
76
|
+
This document and the first-run notice describe ALL paths. The model is unified:
|
|
77
|
+
one opt-in enables them and one opt-out (which always wins) disables them.
|
|
53
78
|
|
|
54
79
|
## What is collected (the whitelist)
|
|
55
80
|
|
|
@@ -90,17 +115,46 @@ prompts, briefs, and diffs can never reach the payload even if a redaction rule
|
|
|
90
115
|
were to miss something. Secrets are additionally scrubbed by the shared redactor
|
|
91
116
|
before whitelisting.
|
|
92
117
|
|
|
93
|
-
## How to opt out
|
|
118
|
+
## How to opt in (and opt back out)
|
|
119
|
+
|
|
120
|
+
Collection is OFF by default. To turn it on, use ANY one of:
|
|
94
121
|
|
|
95
|
-
|
|
122
|
+
- Run `loki telemetry on` (persists `TELEMETRY_ENABLED=true` to `~/.loki/config`)
|
|
123
|
+
- Set the environment variable `LOKI_TELEMETRY=on` (exact word `on`,
|
|
124
|
+
case-insensitive; values like `1` or `true` do NOT count as consent)
|
|
125
|
+
|
|
126
|
+
To opt back out at any time, use ANY one of the following. Opt-out always wins
|
|
127
|
+
over opt-in, so setting one of these guarantees nothing is collected or sent:
|
|
96
128
|
|
|
97
|
-
- Set the environment variable `LOKI_TELEMETRY=off`
|
|
98
129
|
- Run `loki telemetry off`
|
|
130
|
+
- Set `LOKI_TELEMETRY=off`
|
|
99
131
|
- Set `DO_NOT_TRACK=1` (the cross-tool community convention)
|
|
100
132
|
- Set `LOKI_TELEMETRY_DISABLED=true`
|
|
101
133
|
|
|
102
|
-
|
|
103
|
-
|
|
134
|
+
### Precedence (exact)
|
|
135
|
+
|
|
136
|
+
1. If any opt-out flag is set, collection is OFF (hard kill, always wins).
|
|
137
|
+
2. Else if any opt-in flag is set, collection is ON.
|
|
138
|
+
3. Otherwise (the default), collection is OFF.
|
|
139
|
+
|
|
140
|
+
### Air-gapped and enterprise deployments
|
|
141
|
+
|
|
142
|
+
Because collection is opt-in, a default install in an air-gapped, GDPR, or
|
|
143
|
+
FedRAMP environment sends us no telemetry or diagnostics: there is nothing to
|
|
144
|
+
turn off because there is nothing on. To make opting in impossible by accident
|
|
145
|
+
across a fleet, bake `LOKI_TELEMETRY_DISABLED=true` (or `DO_NOT_TRACK=1`) into
|
|
146
|
+
your base image or CI environment; opt-out always wins regardless of any later
|
|
147
|
+
opt-in.
|
|
148
|
+
|
|
149
|
+
This same gate covers ALL paths: the `npm install` event, CLI session and
|
|
150
|
+
command events, the dashboard event, the welcome form, and local crash capture.
|
|
151
|
+
|
|
152
|
+
### OpenTelemetry (separate, self-hosted)
|
|
153
|
+
|
|
154
|
+
`loki telemetry enable [endpoint]` and `LOKI_OTEL_ENDPOINT` configure optional
|
|
155
|
+
OpenTelemetry tracing to an endpoint YOU run. There is no default endpoint, so
|
|
156
|
+
this never egresses to us; it is opt-in by definition and points only where you
|
|
157
|
+
tell it to.
|
|
104
158
|
|
|
105
159
|
## Where reports are stored locally
|
|
106
160
|
|
|
@@ -129,12 +183,16 @@ that choice plainly so you can decide whether to opt out.
|
|
|
129
183
|
|
|
130
184
|
## Compliance posture
|
|
131
185
|
|
|
186
|
+
- Opt-in by default: nothing is collected or sent unless the user explicitly
|
|
187
|
+
opts in. A default install (including air-gapped) sends us no telemetry or
|
|
188
|
+
diagnostics.
|
|
132
189
|
- Anonymous by design: no PII is in the whitelist; emails and IP addresses are
|
|
133
|
-
denied outright.
|
|
190
|
+
denied outright. The welcome form's role / company-size / tools fields are
|
|
191
|
+
self-reported and anonymous (no name, email, or IP).
|
|
134
192
|
- Disclosed: this document plus a first-run notice describe collection before
|
|
135
193
|
any egress occurs.
|
|
136
|
-
- Opt-out is persistent
|
|
137
|
-
collection
|
|
194
|
+
- Opt-out is persistent, friction-free, and ALWAYS wins over opt-in. It applies
|
|
195
|
+
to every collection path.
|
|
138
196
|
- The project id is non-reversible (one-way hash).
|
|
139
197
|
- Deletion: you can delete local reports yourself by removing files under
|
|
140
198
|
`.loki/crash/`.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Status: SEAMS implemented (this worktree). NOT a live hosted backend.
|
|
4
4
|
|
|
5
5
|
R9 in the competitive-stickiness arc is the open-core monetization layer: keep
|
|
6
|
-
Loki fully
|
|
7
|
-
and paid plans would attach later. R9 ships the seams only. There is no Loki
|
|
6
|
+
Loki fully source-available (BUSL-1.1) and free to self-host, while adding the
|
|
7
|
+
SEAMS where hosted, enterprise, and paid plans would attach later. R9 ships the seams only. There is no Loki
|
|
8
8
|
hosted service, no license-verification backend, and no paid gate on any
|
|
9
9
|
existing feature. Every honest stub is labeled as such.
|
|
10
10
|
|