@tera-system/pro 0.1.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.
Files changed (170) hide show
  1. package/LICENSE.md +59 -0
  2. package/MANIFEST.json +24 -0
  3. package/README.md +190 -0
  4. package/RELEASES.md +44 -0
  5. package/WELCOME.md +35 -0
  6. package/agents/application-blueprint.md +412 -0
  7. package/agents/auditor.md +670 -0
  8. package/agents/design-reviewer.md +397 -0
  9. package/agents/domain-expert-agent.md +510 -0
  10. package/agents/domain-research-agent.md +360 -0
  11. package/agents/engineering-agent-dotnet.md +222 -0
  12. package/agents/engineering-agent-phaser.md +279 -0
  13. package/agents/engineering-agent-typescript.md +304 -0
  14. package/agents/engineering-agent.md +147 -0
  15. package/agents/monitor.md +400 -0
  16. package/agents/production-erp-expert.md +506 -0
  17. package/agents/project-knowledge-agent.md +271 -0
  18. package/agents/qa-agent.md +503 -0
  19. package/agents/tera-business-transformation-consultant.md +290 -0
  20. package/agents/tera-client-engagement.md +891 -0
  21. package/agents/tera-software-designer.md +242 -0
  22. package/agents/tera-strategic-advisor.md +325 -0
  23. package/agents/tera-system-evolution.md +764 -0
  24. package/agents/tera.md +526 -0
  25. package/agents/ui-designer.md +431 -0
  26. package/commands/tera-approve.md +36 -0
  27. package/commands/tera-diagnose.md +49 -0
  28. package/commands/tera-gate.md +42 -0
  29. package/commands/tera-help.md +43 -0
  30. package/commands/tera-new-project.md +37 -0
  31. package/commands/tera-plan.md +36 -0
  32. package/commands/tera-request-build.md +47 -0
  33. package/commands/tera-resume.md +36 -0
  34. package/commands/tera-review.md +54 -0
  35. package/commands/tera-status.md +40 -0
  36. package/commands/tera-update.md +50 -0
  37. package/core/project-control/templates/TASK_TEMPLATE.md +146 -0
  38. package/core/tera-system/AGENT_ACTIVATION_MATRIX.md +285 -0
  39. package/core/tera-system/AGENT_DEPENDENCY_MAP.md +117 -0
  40. package/core/tera-system/AGENT_GENERATION_TEMPLATE.md +312 -0
  41. package/core/tera-system/AGENT_PERMISSION_MODEL.md +343 -0
  42. package/core/tera-system/AIS_PROTOCOL.md +191 -0
  43. package/core/tera-system/TERA_AGENT_CONDUCT.md +102 -0
  44. package/core/tera-system/TERA_CONTINUOUS_IMPROVEMENT_POLICY.md +111 -0
  45. package/core/tera-system/TERA_DISTRIBUTION_POLICY.md +291 -0
  46. package/core/tera-system/TERA_LICENSE_GUARD.md +144 -0
  47. package/core/tera-system/TERA_PROJECT_DECISION.md +281 -0
  48. package/core/tera-system/TERA_USER_GUIDE.md +462 -0
  49. package/core/tera-system/TOOLING_AND_MCP_POLICY.md +285 -0
  50. package/core/tera-system/TeraApplicationQuestionBank.md +362 -0
  51. package/core/tera-system/TeraArchitectureMap.md +91 -0
  52. package/core/tera-system/TeraClientPolicy.md +366 -0
  53. package/core/tera-system/TeraHelperAgents.md +970 -0
  54. package/core/tera-system/TeraPolicyMap.md +131 -0
  55. package/core/tera-system/TeraPreExecutionGate.md +818 -0
  56. package/core/tera-system/TeraPreparationDocumentationGovernance.md +370 -0
  57. package/core/tera-system/TeraPricingPolicy.md +674 -0
  58. package/core/tera-system/TeraProjectIntakePolicy.md +164 -0
  59. package/core/tera-system/TeraScenarioStressTests.md +168 -0
  60. package/core/tera-system/TeraSubAgents.md +854 -0
  61. package/core/tera-system/TeraSystemMaintenanceChecklist.md +80 -0
  62. package/core/tera-system/TeraTokenPolicy.md +362 -0
  63. package/core/tera-system/Tera_Project_Preparation_Files.md +1045 -0
  64. package/core/tera-system/agent-helpers/application-blueprint-details.md +177 -0
  65. package/core/tera-system/client-helpers/tera-client-engagement-discovery-domains.md +99 -0
  66. package/core/tera-system/client-helpers/tera-client-engagement-gates.md +258 -0
  67. package/core/tera-system/client-helpers/tera-client-engagement-pricing.md +341 -0
  68. package/core/tera-system/client-helpers/tera-client-engagement-protocols.md +692 -0
  69. package/core/tera-system/consulting-helpers/BTCA_METHODOLOGY_FRAMEWORK.md +195 -0
  70. package/core/tera-system/consulting-helpers/BTCA_REPORT_TEMPLATES.md +266 -0
  71. package/core/tera-system/design-system/ACCESSIBILITY_RULES.md +31 -0
  72. package/core/tera-system/design-system/COMPONENT_LIBRARY_SCHEMA.md +46 -0
  73. package/core/tera-system/design-system/DESIGN_MD_INTEGRATION.md +59 -0
  74. package/core/tera-system/design-system/DESIGN_REVIEW_STANDARDS.md +241 -0
  75. package/core/tera-system/design-system/DESIGN_SOURCE_PROTOCOL.md +61 -0
  76. package/core/tera-system/design-system/DESIGN_SYSTEM_OVERVIEW.md +66 -0
  77. package/core/tera-system/design-system/DESIGN_TOKENS_SCHEMA.md +66 -0
  78. package/core/tera-system/design-system/EXTERNAL_REFERENCE_ANALYSIS.md +52 -0
  79. package/core/tera-system/design-system/FIGMA_INTEGRATION.md +138 -0
  80. package/core/tera-system/design-system/INTERNAL_KITS_INDEX.md +26 -0
  81. package/core/tera-system/design-system/LAYOUT_PATTERNS.md +52 -0
  82. package/core/tera-system/design-system/MOBILE_UI_UX_STANDARDS.md +342 -0
  83. package/core/tera-system/design-system/RTL_LTR_RULES.md +39 -0
  84. package/core/tera-system/design-system/UI_ACCEPTANCE_GATE.md +80 -0
  85. package/core/tera-system/design-system/kits/KIT_ADMIN_DASHBOARD.md +102 -0
  86. package/core/tera-system/engineering-governance/ENGINEERING_AGENT_RESPONSIBILITIES.md +210 -0
  87. package/core/tera-system/engineering-governance/ENGINEERING_BEST_PRACTICES.md +468 -0
  88. package/core/tera-system/engineering-governance/ENGINEERING_GOVERNANCE_GATE.md +131 -0
  89. package/core/tera-system/engineering-governance/ENGINEERING_REVIEW_CHECKLIST.md +129 -0
  90. package/core/tera-system/engineering-governance/QUALITY_GATE_THRESHOLDS.md +159 -0
  91. package/core/tera-system/engineering-helpers/engineering-agent-core.md +171 -0
  92. package/core/tera-system/knowledge-base/OPENHANDS_ARCHITECTURE_REFERENCE.md +243 -0
  93. package/core/tera-system/knowledge-base/manufacturing/00_INDEX.md +32 -0
  94. package/core/tera-system/knowledge-base/manufacturing/01_MANUFACTURING_ERP_CORE_CONCEPTS.md +7 -0
  95. package/core/tera-system/knowledge-base/manufacturing/02_SAP_MANUFACTURING_RESEARCH.md +7 -0
  96. package/core/tera-system/knowledge-base/manufacturing/03_DYNAMICS_365_MANUFACTURING_RESEARCH.md +7 -0
  97. package/core/tera-system/knowledge-base/manufacturing/04_ORACLE_MANUFACTURING_RESEARCH.md +7 -0
  98. package/core/tera-system/knowledge-base/manufacturing/05_ODOO_MANUFACTURING_RESEARCH.md +7 -0
  99. package/core/tera-system/knowledge-base/manufacturing/06_ERPNEXT_MANUFACTURING_RESEARCH.md +7 -0
  100. package/core/tera-system/knowledge-base/manufacturing/07_MANUFACTURING_COSTING_GUIDE.md +7 -0
  101. package/core/tera-system/knowledge-base/manufacturing/08_PRODUCTION_DISCOVERY_QUESTIONS.md +7 -0
  102. package/core/tera-system/knowledge-base/manufacturing/09_MANUFACTURING_BLUEPRINT_CHECKLIST.md +7 -0
  103. package/core/tera-system/knowledge-base/manufacturing/10_PRODUCTION_TEST_SCENARIOS.md +7 -0
  104. package/core/tera-system/knowledge-base/manufacturing/11_QUALITY_REWORK_AND_SCRAP_GUIDE.md +7 -0
  105. package/core/tera-system/knowledge-base/manufacturing/12_MRP_AND_PLANNING_GUIDE.md +7 -0
  106. package/core/tera-system/knowledge-base/manufacturing/13_LOCAL_AND_REGIONAL_MANUFACTURING_CONTEXT.md +7 -0
  107. package/core/tera-system/knowledge-base/manufacturing/14_VENDOR_COMPARISON_MATRIX.md +7 -0
  108. package/core/tera-system/product-standards/maintenance-apps/BEST_PRACTICES_DOMAIN.md +325 -0
  109. package/core/tera-system/product-standards/maintenance-apps/STANDARD_DEFINITION.md +142 -0
  110. package/core/tera-system/profiles/PROFILES_INDEX.md +57 -0
  111. package/core/tera-system/profiles/TEMPLATE.md +47 -0
  112. package/core/tera-system/profiles/dotnet-blazor-ef.md +76 -0
  113. package/core/tera-system/profiles/dotnet-razorpages-adonet.md +137 -0
  114. package/core/tera-system/profiles/dotnet-wpf-sqlite.md +159 -0
  115. package/core/tera-system/profiles/effect-bun-opencode.md +109 -0
  116. package/core/tera-system/profiles/flutter-mobile.md +369 -0
  117. package/core/tera-system/profiles/nextjs-prisma.md +110 -0
  118. package/core/tera-system/profiles/phaser-react-node.md +302 -0
  119. package/core/tera-system/profiles/react-pwa.md +97 -0
  120. package/core/tera-system/runtime/CLIENT_DISCOVERY_PROTOCOL.md +145 -0
  121. package/core/tera-system/runtime/DOMAIN_INTELLIGENCE_PROTOCOL.md +124 -0
  122. package/core/tera-system/runtime/MVP_DEFINITION_PROTOCOL.md +176 -0
  123. package/core/tera-system/runtime/TERA_RUNTIME_CHECKLISTS.md +646 -0
  124. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS.md +50 -0
  125. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS_CLIENT.md +355 -0
  126. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS_CORE.md +799 -0
  127. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES.md +908 -0
  128. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES_DELIVERY.md +584 -0
  129. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES_PREPARATION.md +376 -0
  130. package/core/tera-system/runtime/TERA_SOLUTION_PREPARATION_PROTOCOL.md +335 -0
  131. package/core/tera-system/runtime/TERA_SOLUTION_PREPARATION_TEMPLATES.md +397 -0
  132. package/core/tera-system/runtime/VERSION_LIFECYCLE_PROTOCOL.md +296 -0
  133. package/core/tera-system/semgrep-rules/README.md +32 -0
  134. package/core/tera-system/semgrep-rules/tera-security.yml +66 -0
  135. package/core/tera-system/semgrep-rules/tera-standards.yml +49 -0
  136. package/core/tera-system/teranoo-ui/README.md +48 -0
  137. package/core/tera-system/teranoo-ui/components/button.tsx +51 -0
  138. package/core/tera-system/teranoo-ui/components/card.tsx +49 -0
  139. package/core/tera-system/teranoo-ui/components/dashboard-layout.tsx +36 -0
  140. package/core/tera-system/teranoo-ui/components/data-table.tsx +146 -0
  141. package/core/tera-system/teranoo-ui/components/empty-state.tsx +31 -0
  142. package/core/tera-system/teranoo-ui/components/kpi-card.tsx +42 -0
  143. package/core/tera-system/teranoo-ui/components/page-header.tsx +25 -0
  144. package/core/tera-system/teranoo-ui/components/search-input.tsx +40 -0
  145. package/core/tera-system/teranoo-ui/components/sidebar.tsx +66 -0
  146. package/core/tera-system/teranoo-ui/components/stats-card.tsx +37 -0
  147. package/core/tera-system/teranoo-ui/registry.json +77 -0
  148. package/core/tera-system/teranoo-ui/styles/teranoo-theme.css +61 -0
  149. package/opencode.tera.example.json +30 -0
  150. package/package.json +43 -0
  151. package/scripts/build-bundle.mjs +118 -0
  152. package/scripts/build.mjs +110 -0
  153. package/scripts/dev/mock-server.mjs +124 -0
  154. package/scripts/install.js +406 -0
  155. package/scripts/lib/bundle.mjs +120 -0
  156. package/scripts/lib/integrity.mjs +195 -0
  157. package/scripts/lib/license-gate.mjs +263 -0
  158. package/scripts/lib/license.mjs +90 -0
  159. package/scripts/lib/public-key.pem +3 -0
  160. package/scripts/preflight.mjs +55 -0
  161. package/scripts/tera-heartbeat.mjs +132 -0
  162. package/scripts/tera-license.mjs +63 -0
  163. package/scripts/tera-sync.mjs +116 -0
  164. package/tools/tera-clean.ps1 +97 -0
  165. package/tools/tera-fetch.ps1 +165 -0
  166. package/tools/tera-release.ps1 +96 -0
  167. package/tools/tera-schedule.ps1 +59 -0
  168. package/tools/tera-update.ps1 +472 -0
  169. package/tools/tera-watch.ps1 +154 -0
  170. package/tools/update-client-repositories.ps1 +92 -0
@@ -0,0 +1,195 @@
1
+ // @tera/core — File integrity subsystem (Phase 4, multi-layer license enforcement)
2
+ // Creates SHA256 manifest of installed files + tamper detection.
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { createHash } from "node:crypto";
6
+
7
+ const INTEGRITY_FILE = "integrity.json";
8
+ const INTEGRITY_BACKUP = "integrity.bak.json";
9
+ const CRITICAL_FILES = [
10
+ "scripts/lib/license.mjs",
11
+ "scripts/lib/license-gate.mjs",
12
+ "scripts/lib/integrity.mjs",
13
+ "scripts/install.js",
14
+ "scripts/preflight.mjs",
15
+ ".tera/license.state.json",
16
+ ];
17
+
18
+ /**
19
+ * Compute SHA256 of a single file.
20
+ */
21
+ export function sha256File(filePath) {
22
+ return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
23
+ }
24
+
25
+ /**
26
+ * Walk a directory and compute hashes of all files.
27
+ */
28
+ export function hashDirectory(dir, baseDir = dir) {
29
+ const result = {};
30
+ if (!fs.existsSync(dir)) return result;
31
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
32
+ const full = path.join(dir, entry.name);
33
+ if (entry.isDirectory()) {
34
+ Object.assign(result, hashDirectory(full, baseDir));
35
+ } else if (entry.isFile()) {
36
+ const rel = path.relative(baseDir, full).split(path.sep).join("/");
37
+ result[rel] = sha256File(full);
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+
43
+ /**
44
+ * Create an integrity manifest of the Tera installation.
45
+ * Stored in two locations to prevent single-point deletion.
46
+ */
47
+ export function createIntegrityManifest(target) {
48
+ const teraDir = path.join(target, "tera-system");
49
+ const opencodeAgents = path.join(target, ".opencode", "agents");
50
+ const opencodeCommands = path.join(target, ".opencode", "commands");
51
+ const scriptsDir = path.join(target, "scripts");
52
+
53
+ const manifest = {
54
+ version: 1,
55
+ createdAt: new Date().toISOString(),
56
+ target,
57
+ fileCount: 0,
58
+ hashes: {},
59
+ criticalHashes: {},
60
+ };
61
+
62
+ // Hash all tera-system files
63
+ if (fs.existsSync(teraDir)) {
64
+ Object.assign(manifest.hashes, hashDirectory(teraDir, target));
65
+ }
66
+ // Hash all agent files
67
+ if (fs.existsSync(opencodeAgents)) {
68
+ Object.assign(manifest.hashes, hashDirectory(opencodeAgents, target));
69
+ }
70
+ // Hash all command files
71
+ if (fs.existsSync(opencodeCommands)) {
72
+ Object.assign(manifest.hashes, hashDirectory(opencodeCommands, target));
73
+ }
74
+ // Hash scripts
75
+ if (fs.existsSync(scriptsDir)) {
76
+ Object.assign(manifest.hashes, hashDirectory(scriptsDir, target));
77
+ }
78
+
79
+ manifest.fileCount = Object.keys(manifest.hashes).length;
80
+
81
+ // Hash critical files separately (with absolute paths for self-check)
82
+ for (const rel of CRITICAL_FILES) {
83
+ const abs = path.join(target, rel);
84
+ if (fs.existsSync(abs)) {
85
+ manifest.criticalHashes[rel] = sha256File(abs);
86
+ }
87
+ }
88
+
89
+ return manifest;
90
+ }
91
+
92
+ /**
93
+ * Save integrity manifest to disk (in two locations for redundancy).
94
+ */
95
+ export function saveIntegrityManifest(target, manifest) {
96
+ const teraDir = path.join(target, ".tera");
97
+ fs.mkdirSync(teraDir, { recursive: true });
98
+ const json = JSON.stringify(manifest, null, 2);
99
+
100
+ // Primary location
101
+ fs.writeFileSync(path.join(teraDir, INTEGRITY_FILE), json);
102
+ // Backup location (hidden, harder to find)
103
+ fs.writeFileSync(path.join(teraDir, INTEGRITY_BACKUP), json);
104
+
105
+ return manifest;
106
+ }
107
+
108
+ /**
109
+ * Read integrity manifest from disk.
110
+ * Tries primary first, falls back to backup.
111
+ */
112
+ export function readIntegrityManifest(target) {
113
+ const teraDir = path.join(target, ".tera");
114
+ for (const filename of [INTEGRITY_FILE, INTEGRITY_BACKUP]) {
115
+ const p = path.join(teraDir, filename);
116
+ if (fs.existsSync(p)) {
117
+ try {
118
+ return JSON.parse(fs.readFileSync(p, "utf8"));
119
+ } catch {
120
+ continue;
121
+ }
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+
127
+ /**
128
+ * Verify integrity — returns list of modified/missing files.
129
+ */
130
+ export function verifyIntegrity(target) {
131
+ const manifest = readIntegrityManifest(target);
132
+ if (!manifest) {
133
+ return { ok: false, reason: "manifest-missing", modified: [], missing: [] };
134
+ }
135
+
136
+ const modified = [];
137
+ const missing = [];
138
+
139
+ for (const [rel, expectedHash] of Object.entries(manifest.hashes)) {
140
+ const abs = path.join(target, rel);
141
+ if (!fs.existsSync(abs)) {
142
+ missing.push(rel);
143
+ continue;
144
+ }
145
+ try {
146
+ const actualHash = sha256File(abs);
147
+ if (actualHash !== expectedHash) {
148
+ modified.push(rel);
149
+ }
150
+ } catch {
151
+ missing.push(rel);
152
+ }
153
+ }
154
+
155
+ return {
156
+ ok: modified.length === 0 && missing.length === 0,
157
+ reason: modified.length > 0 ? "files-modified" : missing.length > 0 ? "files-missing" : "ok",
158
+ modified,
159
+ missing,
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Verify critical files (license-gate.mjs, integrity.mjs, etc.)
165
+ * Detects tampering with the gate itself.
166
+ */
167
+ export function verifyCriticalFiles(target) {
168
+ const manifest = readIntegrityManifest(target);
169
+ if (!manifest || !manifest.criticalHashes) {
170
+ return { ok: false, reason: "no-critical-manifest", tampered: [] };
171
+ }
172
+
173
+ const tampered = [];
174
+ for (const [rel, expectedHash] of Object.entries(manifest.criticalHashes)) {
175
+ const abs = path.join(target, rel);
176
+ if (!fs.existsSync(abs)) {
177
+ tampered.push({ file: rel, reason: "missing" });
178
+ continue;
179
+ }
180
+ try {
181
+ const actualHash = sha256File(abs);
182
+ if (actualHash !== expectedHash) {
183
+ tampered.push({ file: rel, reason: "modified" });
184
+ }
185
+ } catch {
186
+ tampered.push({ file: rel, reason: "unreadable" });
187
+ }
188
+ }
189
+
190
+ return {
191
+ ok: tampered.length === 0,
192
+ reason: tampered.length > 0 ? "critical-tampered" : "ok",
193
+ tampered,
194
+ };
195
+ }
@@ -0,0 +1,263 @@
1
+ // @tera/core — Multi-layer License Gate (Phase 4)
2
+ // Enforces license validity + detects tampering + periodic re-verification.
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { assessLicenseState, parseLicenseKey } from "./license.mjs";
6
+ import { verifyIntegrity, verifyCriticalFiles, readIntegrityManifest } from "./integrity.mjs";
7
+
8
+ export const GATE_RESULTS = {
9
+ ALLOW: "allow",
10
+ DENY_NO_LICENSE: "deny-no-license",
11
+ DENY_EXPIRED: "deny-expired",
12
+ DENY_INVALID: "deny-invalid",
13
+ DENY_TAMPERED: "deny-tampered",
14
+ DENY_INTEGRITY: "deny-integrity",
15
+ DENY_NEEDS_RECHECK: "deny-needs-recheck",
16
+ };
17
+
18
+ const RECHECK_INTERVAL_DAYS = 30;
19
+ const RECHECK_INTERVAL_MS = RECHECK_INTERVAL_DAYS * 86400000;
20
+
21
+ /**
22
+ * Try to verify license via API (online check).
23
+ * Returns null if API unreachable.
24
+ */
25
+ async function verifyViaAPI(licenseKey, apiUrl) {
26
+ if (!licenseKey || !apiUrl) return null;
27
+ try {
28
+ const controller = new AbortController();
29
+ const timeout = setTimeout(() => controller.abort(), 5000);
30
+ const res = await fetch(`${apiUrl}/verify`, {
31
+ method: "POST",
32
+ headers: { "Content-Type": "application/json" },
33
+ body: JSON.stringify({ licenseKey }),
34
+ signal: controller.signal,
35
+ });
36
+ clearTimeout(timeout);
37
+ if (!res.ok) return null;
38
+ return await res.json();
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Layer 1: License state check
46
+ */
47
+ function checkLicenseState(target) {
48
+ const stateFile = path.join(target, ".tera", "license.state.json");
49
+ if (!fs.existsSync(stateFile)) {
50
+ return { ok: false, reason: "no-state-file", state: "Unlicensed" };
51
+ }
52
+ try {
53
+ const state = JSON.parse(fs.readFileSync(stateFile, "utf8"));
54
+ return { ok: true, state };
55
+ } catch {
56
+ return { ok: false, reason: "state-corrupted", state: "Invalid" };
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Layer 2: Check if periodic re-verification is due
62
+ */
63
+ function checkRecheckDue(state) {
64
+ if (!state || !state.checkedAt) return { due: true };
65
+ const lastCheck = new Date(state.checkedAt).getTime();
66
+ if (!Number.isFinite(lastCheck)) return { due: true };
67
+ const now = Date.now();
68
+ const age = now - lastCheck;
69
+ return { due: age > RECHECK_INTERVAL_MS, ageDays: age / 86400000 };
70
+ }
71
+
72
+ /**
73
+ * Layer 3: Critical file integrity (gate itself)
74
+ */
75
+ function checkCriticalFiles(target) {
76
+ return verifyCriticalFiles(target);
77
+ }
78
+
79
+ /**
80
+ * Layer 4: Full file integrity
81
+ */
82
+ function checkAllFiles(target) {
83
+ return verifyIntegrity(target);
84
+ }
85
+
86
+ /**
87
+ * Multi-layer license gate enforcement.
88
+ * Returns: { allow, result, reason, state, ... }
89
+ *
90
+ * Hard denial cases (always block):
91
+ * - Unlicensed / Invalid / Expired license state
92
+ * - Critical files tampered (license-gate.mjs, integrity.mjs, etc.)
93
+ * - Integrity manifest missing
94
+ *
95
+ * Soft denial cases (warn but allow in Grace):
96
+ * - Periodic recheck overdue (can run, but warns)
97
+ */
98
+ export async function enforceLicenseGate(target, options = {}) {
99
+ const apiUrl = options.apiUrl || process.env.TERA_LICENSE_API || "https://teranoo.com/api/license";
100
+ const offlineOnly = process.env.TERA_OFFLINE_ONLY === "1";
101
+ const reason = { layers: [] };
102
+
103
+ // ─── Layer 1: License state ──────────────────────────────────────
104
+ const stateCheck = checkLicenseState(target);
105
+ if (!stateCheck.ok) {
106
+ return {
107
+ allow: false,
108
+ result: GATE_RESULTS.DENY_NO_LICENSE,
109
+ reason: stateCheck.reason,
110
+ message: "❌ License state file missing or corrupted. Reinstall Tera System.",
111
+ };
112
+ }
113
+
114
+ const lic = stateCheck.state;
115
+ reason.layers.push({ layer: "license-state", state: lic.state, reason: lic.reason });
116
+
117
+ // Hard block: Unlicensed
118
+ if (lic.state === "Unlicensed") {
119
+ return {
120
+ allow: false,
121
+ result: GATE_RESULTS.DENY_NO_LICENSE,
122
+ reason: "unlicensed",
123
+ state: lic,
124
+ message:
125
+ "❌ Tera System License Required.\n" +
126
+ "Current state: Unlicensed (trial mode).\n\n" +
127
+ "Visit https://teranoo.com/pricing to obtain a license key.\n" +
128
+ "Then run: npm run tera-license -- activate <key>",
129
+ };
130
+ }
131
+
132
+ // Hard block: Invalid
133
+ if (lic.state === "Invalid") {
134
+ return {
135
+ allow: false,
136
+ result: GATE_RESULTS.DENY_INVALID,
137
+ reason: lic.reason || "invalid",
138
+ state: lic,
139
+ message:
140
+ "❌ Tera System License Invalid.\n" +
141
+ `Reason: ${lic.reason}\n\n` +
142
+ "Your license key is not valid. Please contact support@teranoo.com.",
143
+ };
144
+ }
145
+
146
+ // Hard block: Expired (past grace)
147
+ if (lic.state === "Expired") {
148
+ return {
149
+ allow: false,
150
+ result: GATE_RESULTS.DENY_EXPIRED,
151
+ reason: "expired",
152
+ state: lic,
153
+ message:
154
+ "❌ Tera System License Expired.\n" +
155
+ `Expired at: ${lic.expiresAt}\n\n` +
156
+ "Visit https://teranoo.com/pricing to renew.",
157
+ };
158
+ }
159
+
160
+ // ─── Layer 2: Critical file integrity (gate self-protection) ─────
161
+ const criticalCheck = checkCriticalFiles(target);
162
+ reason.layers.push({ layer: "critical-files", ok: criticalCheck.ok });
163
+ if (!criticalCheck.ok) {
164
+ return {
165
+ allow: false,
166
+ result: GATE_RESULTS.DENY_TAMPERED,
167
+ reason: "critical-files-tampered",
168
+ tampered: criticalCheck.tampered,
169
+ message:
170
+ "❌ Tera System files have been tampered with.\n" +
171
+ `Tampered files: ${criticalCheck.tampered.map((t) => t.file).join(", ")}\n\n` +
172
+ "Reinstall required: npm install @tera-system/core@latest",
173
+ };
174
+ }
175
+
176
+ // ─── Layer 3: Full integrity (deep tamper detection) ────────────
177
+ const integrityCheck = checkAllFiles(target);
178
+ reason.layers.push({
179
+ layer: "integrity",
180
+ ok: integrityCheck.ok,
181
+ modified: integrityCheck.modified?.length || 0,
182
+ missing: integrityCheck.missing?.length || 0,
183
+ });
184
+ if (!integrityCheck.ok) {
185
+ return {
186
+ allow: false,
187
+ result: GATE_RESULTS.DENY_INTEGRITY,
188
+ reason: integrityCheck.reason,
189
+ modified: integrityCheck.modified,
190
+ missing: integrityCheck.missing,
191
+ message:
192
+ "❌ Tera System installation has been modified.\n" +
193
+ `Modified files: ${integrityCheck.modified?.length || 0}\n` +
194
+ `Missing files: ${integrityCheck.missing?.length || 0}\n\n` +
195
+ "Reinstall required to restore integrity.",
196
+ };
197
+ }
198
+
199
+ // ─── Layer 4: Periodic re-verification ───────────────────────────
200
+ const recheck = checkRecheckDue(lic);
201
+ if (recheck.due && !offlineOnly) {
202
+ // Try API re-verification
203
+ const apiResult = await verifyViaAPI(lic.licenseKey, apiUrl);
204
+ if (apiResult && apiResult.valid === false) {
205
+ return {
206
+ allow: false,
207
+ result: GATE_RESULTS.DENY_INVALID,
208
+ reason: "api-revoked",
209
+ state: lic,
210
+ message:
211
+ "❌ License revoked by server.\n" +
212
+ `Reason: ${apiResult.reason || "revoked"}\n\n` +
213
+ "Contact support@teranoo.com.",
214
+ };
215
+ }
216
+ // Update last check timestamp
217
+ lic.checkedAt = new Date().toISOString();
218
+ fs.writeFileSync(path.join(target, ".tera", "license.state.json"), JSON.stringify(lic, null, 2));
219
+ }
220
+
221
+ // ─── Grace period: allow with warning ────────────────────────────
222
+ if (lic.state === "Grace") {
223
+ return {
224
+ allow: true,
225
+ result: GATE_RESULTS.ALLOW,
226
+ warning: true,
227
+ state: lic,
228
+ reason: "grace-period",
229
+ message: `⚠️ License in grace period. Expired at ${lic.expiresAt}. Renew soon.`,
230
+ };
231
+ }
232
+
233
+ // ─── Active license: full allow ─────────────────────────────────
234
+ return {
235
+ allow: true,
236
+ result: GATE_RESULTS.ALLOW,
237
+ state: lic,
238
+ reason: "active",
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Synchronous version for quick checks (skips API re-verification).
244
+ */
245
+ export function enforceLicenseGateSync(target) {
246
+ const stateCheck = checkLicenseState(target);
247
+ if (!stateCheck.ok) {
248
+ return { allow: false, result: GATE_RESULTS.DENY_NO_LICENSE, reason: stateCheck.reason };
249
+ }
250
+ const lic = stateCheck.state;
251
+ if (lic.state === "Unlicensed" || lic.state === "Invalid" || lic.state === "Expired") {
252
+ return { allow: false, result: GATE_RESULTS[`DENY_${lic.state.toUpperCase()}`], state: lic };
253
+ }
254
+ const criticalCheck = checkCriticalFiles(target);
255
+ if (!criticalCheck.ok) {
256
+ return { allow: false, result: GATE_RESULTS.DENY_TAMPERED, tampered: criticalCheck.tampered };
257
+ }
258
+ const integrityCheck = checkAllFiles(target);
259
+ if (!integrityCheck.ok) {
260
+ return { allow: false, result: GATE_RESULTS.DENY_INTEGRITY };
261
+ }
262
+ return { allow: true, result: GATE_RESULTS.ALLOW, state: lic, warning: lic.state === "Grace" };
263
+ }
@@ -0,0 +1,90 @@
1
+ // @tera/core license subsystem — offline Ed25519 validation (Phase 3, SCP-005)
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import crypto from "node:crypto";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const PUBLIC_KEY_PATH = path.join(__dirname, "public-key.pem");
9
+
10
+ export const STATES = {
11
+ UNLICENSED: "Unlicensed",
12
+ ACTIVE: "Active",
13
+ GRACE: "Grace",
14
+ EXPIRED: "Expired",
15
+ INVALID: "Invalid",
16
+ };
17
+ export const GRACE_DAYS = 7;
18
+
19
+ function loadPublicKey() {
20
+ return fs.readFileSync(PUBLIC_KEY_PATH, "utf8");
21
+ }
22
+
23
+ /**
24
+ * Issue a signed license key (used by the issuer — Majed).
25
+ * Pricing model is DATA (tier/project/expiry/seats), never code.
26
+ */
27
+ export function issueLicenseKey({ tier, project, issuedAt, expiresAt, seats = 1 }, privateKeyText) {
28
+ const payload = { v: 1, tier, project, issuedAt, expiresAt, seats };
29
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
30
+ const sig = crypto.sign(null, Buffer.from(payloadB64), privateKeyText).toString("base64url");
31
+ return `${payloadB64}.${sig}`;
32
+ }
33
+
34
+ /** Verify signature (client-side, offline) — public key ships in package. */
35
+ export function parseLicenseKey(key) {
36
+ if (typeof key !== "string" || !key.includes(".")) return { ok: false, reason: "malformed", payload: null };
37
+ const [payloadB64, sigB64] = key.split(".");
38
+ let payload;
39
+ try {
40
+ payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
41
+ } catch {
42
+ return { ok: false, reason: "bad-payload", payload: null };
43
+ }
44
+ let valid = false;
45
+ try {
46
+ valid = crypto.verify(null, Buffer.from(payloadB64), loadPublicKey(), Buffer.from(sigB64, "base64url"));
47
+ } catch {
48
+ valid = false;
49
+ }
50
+ if (!valid) return { ok: false, reason: "bad-signature", payload };
51
+ return { ok: true, reason: "valid", payload };
52
+ }
53
+
54
+ /** Map a key + now → one of the 6 license states (with offline grace period). */
55
+ export function assessLicenseState(key, { now = new Date(), graceDays = GRACE_DAYS } = {}) {
56
+ if (!key) return { state: STATES.UNLICENSED, reason: "no-key-trial" };
57
+ const parsed = parseLicenseKey(key);
58
+ if (!parsed.ok) return { state: STATES.INVALID, reason: parsed.reason };
59
+ const t = parsed.payload;
60
+ const expires = new Date(t.expiresAt).getTime();
61
+ const nowMs = now.getTime();
62
+ if (!Number.isFinite(expires)) return { state: STATES.INVALID, reason: "bad-expiry", payload: t };
63
+ const graceMs = graceDays * 86400000;
64
+ if (nowMs <= expires) {
65
+ return { state: STATES.ACTIVE, reason: "valid", payload: t, expiresAt: t.expiresAt };
66
+ }
67
+ if (nowMs <= expires + graceMs) {
68
+ return {
69
+ state: STATES.GRACE,
70
+ reason: "expired-within-grace",
71
+ payload: t,
72
+ expiresAt: t.expiresAt,
73
+ graceUntil: new Date(expires + graceMs).toISOString(),
74
+ };
75
+ }
76
+ return { state: STATES.EXPIRED, reason: "expired", payload: t, expiresAt: t.expiresAt };
77
+ }
78
+
79
+ export function readLicenseState(stateFile) {
80
+ try {
81
+ return JSON.parse(fs.readFileSync(stateFile, "utf8"));
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ export function writeLicenseState(stateFile, state) {
88
+ fs.mkdirSync(path.dirname(stateFile), { recursive: true });
89
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
90
+ }
@@ -0,0 +1,3 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MCowBQYDK2VwAyEARWylLsXN1N2/C3QiWt5QjsWmGD62fg79IilkrXmzW0k=
3
+ -----END PUBLIC KEY-----
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tera/core — Preflight Check (runs before any agent/command)
4
+ * --------------------------------------------------------------
5
+ * Can be called manually: `node scripts/preflight.mjs`
6
+ * Or wired into custom hooks by the OpenCode plugin system.
7
+ *
8
+ * Returns exit code 0 if allowed, non-zero if denied.
9
+ */
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { enforceLicenseGate } from "./lib/license-gate.mjs";
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+
17
+ function resolveTarget() {
18
+ const args = process.argv.slice(2);
19
+ for (let i = 0; i < args.length; i++) {
20
+ if (args[i] === "--target" && args[i + 1]) return path.resolve(args[i + 1]);
21
+ if (args[i] === "--help") {
22
+ console.log("Usage: node scripts/preflight.mjs [--target <dir>] [--quiet]");
23
+ process.exit(0);
24
+ }
25
+ }
26
+ return path.resolve(process.env.TERA_TARGET_DIR || process.env.INIT_CWD || process.cwd());
27
+ }
28
+
29
+ const TARGET = resolveTarget();
30
+ const QUIET = process.argv.includes("--quiet");
31
+
32
+ (async () => {
33
+ try {
34
+ const result = await enforceLicenseGate(TARGET);
35
+
36
+ if (!QUIET) {
37
+ if (result.allow) {
38
+ if (result.warning) {
39
+ console.log(`⚠️ Tera License Gate: ALLOWED (warning)`);
40
+ if (result.message) console.log(` ${result.message}`);
41
+ } else {
42
+ console.log(`✅ Tera License Gate: ALLOWED (state=${result.state?.state || "Active"})`);
43
+ }
44
+ } else {
45
+ console.log(`\n${result.message || "❌ License gate denied."}\n`);
46
+ }
47
+ }
48
+
49
+ process.exit(result.allow ? 0 : 1);
50
+ } catch (err) {
51
+ if (!QUIET) console.log(`⚠️ Preflight error: ${err.message}`);
52
+ // Fail closed on unexpected errors
53
+ process.exit(1);
54
+ }
55
+ })();