@tera-system/pro 0.2.10 → 0.2.12

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/LICENSE.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## النموذج: كود مفتوح + ترخيص مدفوع
6
6
 
7
- هذا الحزمة (`@tera-system/core`) مفتوحة المصدر لكنها **خاضعة لترخيص تجاري**.
7
+ منظومة Tera (`@tera-system/pro`) نظام تجاري مرخّص — كود مفتوح المصدر لكنه **خاضع لترخيص تجاري**.
8
8
 
9
9
  ### ماذا يعني ذلك عملياً:
10
10
 
package/MANIFEST.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tera-system/pro",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Build manifest for the Tera System Pro commercial plugin component (THIN mode — content served from teranoo.com signed bundle).",
5
5
  "compatibleEnvironments": [
6
6
  "opencode"
@@ -15,7 +15,7 @@
15
15
  "projectControlTemplates": 1,
16
16
  "assets": 49
17
17
  },
18
- "bundleRef": "core-bundle-v0.2.10.signed.json (SHA256 4F776E78BC2CA99725DD320B9B9EC2C4F05200B52ED7B857BEE487A5F8FD4B13)",
18
+ "bundleRef": "core-bundle-v0.2.11.signed.json (SHA256 B05E0A99811958BE44DFB162812366723D3080145122A3C3E0C345E37E2E51B2)",
19
19
  "builtAt": "2026-08-28",
20
20
  "sha256": {
21
21
  "agents": "9769b3acc15e44e8ae56e4d585f7b965f6c30eafceea509edd0dfe3ce1d83270",
package/README.md CHANGED
@@ -20,7 +20,7 @@ npm install @tera-system/pro
20
20
  > ```
21
21
  > أو مباشرة بدون موافقة:
22
22
  > ```bash
23
- > npx tera-restore
23
+ > npx tera-pro-restore
24
24
  > # أو
25
25
  > node node_modules/@tera-system/pro/scripts/install.js
26
26
  > ```
@@ -54,21 +54,21 @@ npm run tera-sync
54
54
  | ط§ظ„ظ†ظˆط§ط© ط§ظ„ط­ظˆظƒظ…ظٹط© (110 ظ…ظ„ظپط§ظ‹) | `tera-system/` |
55
55
  | ظ‡ظٹظƒظ„ `project-control/` (ظ‚ظˆط§ظ„ط¨) | `project-control/` *(ظپظ‚ط· ط¥ظ† ظ„ظ… ظٹظˆط¬ط¯ — ط­ط§ظ„ط© ط®ط§طµط© ط¨ظƒظ„ ط¹ظ…ظٹظ„)* |
56
56
  | ط£ط¯ظˆط§طھ ط§ظ„طھظˆط²ظٹط¹ (7 ط³ظƒط±ط¨طھط§طھ) | `tools/` |
57
- | ط³ط¬ظ„ ط§ظ„ظ…ظ„ظƒظٹط© | `.opencode/tera-core.manifest.json` |
57
+ | ط³ط¬ظ„ ط§ظ„ظ…ظ„ظƒظٹط© | `.opencode/tera.manifest.json` |
58
58
 
59
59
  ## ط³ظ„ظˆظƒ ط§ظ„ظ…ط«ط¨ظ‘طھ (ط؛ظٹط± ظ…ط¯ظ…ط± + ظˆط§ط¹ظچ ط¨ط§ظ„ظ…ظ„ظƒظٹط©)
60
60
 
61
61
  - **ظ„ط§ ظٹط­ط°ظپ ط£ظٹ ط´ظٹط، ط£ط¨ط¯ط§ظ‹.**
62
62
  - **ظ„ط§ ظٹظƒطھط¨ ظپظˆظ‚ ط£ظٹ ظ…ظ„ظپ ط؛ظٹط± ظ…ظ…ظ„ظˆظƒ ظ„ظ‡:** ط£ظٹ ظ…ظ„ظپ ظ…ظˆط¬ظˆط¯ ط¨ط§ط³ظ… ظٹطھط¹ط§ط±ط¶ ظ…ط¹ ط­ط²ظ…ط© `@tera-system/pro` (ظˆظ„ظ… طھط«ط¨ظ‘طھظ‡ ط§ظ„ط­ط²ظ…ط© ط³ط§ط¨ظ‚ط§ظ‹) ظٹظڈطھط±ظƒ ظƒظ…ط§ ظ‡ظˆ ظˆظٹظڈط³ط¬ظژظ‘ظ„ ظƒظ€ `CONFLICT` ظپظٹ طھظ‚ط±ظٹط± ط§ظ„طھط±ظƒظٹط¨.
63
- - **ظٹظپط±ظ‘ظ‚ ط¨ظٹظ† ظ…ظ„ظپط§طھظ‡ ظˆظ…ظ„ظپط§طھ ط§ظ„ظ…ط³طھط®ط¯ظ…** ط¹ط¨ط± ط³ط¬ظ„ ط§ظ„ظ…ظ„ظƒظٹط© `.opencode/tera-core.manifest.json`.
63
+ - **ظٹظپط±ظ‘ظ‚ ط¨ظٹظ† ظ…ظ„ظپط§طھظ‡ ظˆظ…ظ„ظپط§طھ ط§ظ„ظ…ط³طھط®ط¯ظ…** ط¹ط¨ط± ط³ط¬ظ„ ط§ظ„ظ…ظ„ظƒظٹط© `.opencode/tera.manifest.json`.
64
64
  - **ط§ظ„ظ…ظ„ظپط§طھ ط§ظ„ظ…ظ…ظ„ظˆظƒط© ظ„ظ„ط­ط²ظ…ط© ظپظ‚ط·** طھظڈط­ط¯ظژظ‘ط« ط¹ظ†ط¯ ط§ط®طھظ„ط§ظپظ‡ط§طŒ ظ…ط¹ **ظ†ط³ط®ط© ط§ط­طھظٹط§ط·ظٹط©** (`*.tera-bak-<ts>`) ظ‚ط¨ظ„ ط£ظٹ طھط­ط¯ظٹط«.
65
65
  - **ط¥ط¹ط§ط¯ط© ط§ظ„طھط´ط؛ظٹظ„ ط¢ظ…ظ†ط© (idempotent):** ط§ظ„ظ…ظ„ظپط§طھ ط§ظ„ظ…طھط·ط§ط¨ظ‚ط© طھظڈطھط®ط·ظ‘ظ‰.
66
66
  - `opencode.json` ط§ظ„ظ…ظˆط¬ظˆط¯ ظ„ط¯ظ‰ ط§ظ„ظ…ط³طھط®ط¯ظ… **ظ„ط§ ظٹظڈظ„ظ…ط³ ط£ط¨ط¯ط§ظ‹** — طھظڈظˆط¶ط¹ ظ†ط³ط®ط© ظ…ط±ط¬ط¹ظٹط© ظپظ‚ط· ظˆطھط¸ظ‡ط± ط§ظ„طھط¹ظ„ظٹظ…ط§طھ ظپظٹ ط§ظ„طھظ‚ط±ظٹط±.
67
67
 
68
68
  ## ط¥ظ„ط؛ط§ط، ط§ظ„طھط«ط¨ظٹطھ (ظٹط¯ظˆظٹ — ط؛ظٹط± ظ…ط¯ظ…ط±)
69
69
 
70
- 1. ط§ط­ط°ظپ ط§ظ„ظ…ظ„ظپط§طھ ط§ظ„ظ…ط³ط¬ظ‘ظ„ط© ظپظٹ `.opencode/tera-core.manifest.json` (`files`).
71
- 2. ط§ط­ط°ظپ `.opencode/tera-core.manifest.json` ظ†ظپط³ظ‡.
70
+ 1. ط§ط­ط°ظپ ط§ظ„ظ…ظ„ظپط§طھ ط§ظ„ظ…ط³ط¬ظ‘ظ„ط© ظپظٹ `.opencode/tera.manifest.json` (`files`).
71
+ 2. ط§ط­ط°ظپ `.opencode/tera.manifest.json` ظ†ظپط³ظ‡.
72
72
  3. ط§ط­طھظپط¸ ط¨ط£ظٹ `*.tera-bak-*` ط£ظˆ ظ…ظ„ظپط§طھظƒ ط§ظ„ط´ط®طµظٹط© ظƒظ…ط§ ظ‡ظٹ.
73
73
 
74
74
  ## ط§ظ„طھط­ظ‚ظ‚ ظ…ظ† ط§ظ„ظ†ط²ط§ظ‡ط©
package/RELEASES.md CHANGED
@@ -5,6 +5,34 @@
5
5
  ---
6
6
 
7
7
 
8
+
9
+
10
+ ## 0.2.12 (2026-08-30)
11
+
12
+ ### Bug fix: preflight false tamper detection (SCP-2026-08-30-002)
13
+
14
+ **What changed (scripts only - bundle unchanged):**
15
+ - integrity.mjs: removed .tera/license.state.json from CRITICAL_FILES - it is a self-modifying STATE file (checkedAt changes on every install/heartbeat/re-check). License security comes from the Ed25519-signed key, not from hashing the state file.
16
+ - install.js: license state is now written BEFORE the integrity snapshot, so the manifest always reflects the final on-disk state.
17
+ - tera-license.mjs: added the 'activate <key> [email]' command (TERA_LICENSE_GUARD.md instructs blocked users to run it - it did not exist).
18
+
19
+ **Why:**
20
+ - preflight.mjs always failed with "Tampered files: .tera/license.state.json" because the install wrote the state file after the snapshot (0.36s gap) - verified and reproduced.
21
+ - No bundle change: the fix is entirely in npm-shipped scripts. Server bundle stays v0.2.11.
22
+
23
+ ## 0.2.11 (2026-08-30)
24
+
25
+ ### Security hardening: license-gate message fix + zero core traces
26
+
27
+ **What changed:**
28
+ - Runtime license-gate tamper message now points to @tera-system/pro (was @tera-system/core)
29
+ - Removed ALL references to any internal package from user-visible files (LICENSE, README, WELCOME, RELEASES)
30
+ - Manifest renamed .opencode/tera-core.manifest.json -> .opencode/tera.manifest.json (auto-migrated on install)
31
+ - Bundle type renamed tera-core-bundle -> tera-bundle (signed, verified)
32
+
33
+ **Why:**
34
+ - Commercial isolation: the licensed product is @tera-system/pro; no trace of other packages remains for clients
35
+
8
36
  ## 0.2.10 (2026-08-29)
9
37
 
10
38
  ### First-sale readiness: full content sync with core 0.2.10 + license gates
@@ -22,12 +50,12 @@
22
50
  ### حل الاستعادة عند الحذف بالخطأ + توافق npm 11
23
51
 
24
52
  **ما تغيّر:**
25
- - إضافة `bin` جديد: `tera-restore` و `tera-pro-restore` — نفس حل core لكن للحزمة التجارية
53
+ - إضافة `bin` جديد: `tera-restore` و `tera-pro-restore` — أمرا استعادة للحزمة التجارية
26
54
  - توثيق `npm approve-scripts @tera-system/pro` كحل لأمان npm 11
27
55
  - قسم "الاستعادة عند الحذف بالخطأ" في README
28
56
 
29
57
  **لماذا هذا التحديث:**
30
- - نفس مشكلة core — حذف `.opencode/agents` بالخطأ كان يحتاج أمراً صريحاً لا يعتمد على `postinstall`
58
+ - حذف `.opencode/agents` بالخطأ كان يحتاج أمراً صريحاً لا يعتمد على `postinstall`
31
59
 
32
60
  ---
33
61
 
package/WELCOME.md CHANGED
@@ -33,7 +33,7 @@
33
33
  ## هل حذفت ملفات بالخطأ؟
34
34
  إذا اختفت ملفات `.opencode/agents/` بعد `npm install` ولم تظهر، شغّل:
35
35
  ```bash
36
- npx tera-restore
36
+ npx tera-pro-restore
37
37
  ```
38
38
  ثم أعد فتح OpenCode.
39
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tera-system/pro",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Tera System Pro — commercial edition (thin): system files are fetched from teranoo.com as a signed bundle; enforced license gate + heartbeat",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",
@@ -92,7 +92,7 @@ const privateKey = loadPrivateKey();
92
92
  const signature = crypto.sign(null, Buffer.from(canonicalHash), privateKey).toString("base64url");
93
93
 
94
94
  const bundle = {
95
- type: "tera-core-bundle",
95
+ type: "tera-bundle",
96
96
  schema: 1,
97
97
  packageName: pkg.name,
98
98
  packageVersion: pkg.version,
@@ -8,7 +8,7 @@
8
8
  * Rules:
9
9
  * - Never deletes anything.
10
10
  * - Never overwrites a file it does not own.
11
- * - Tracks ownership via `.opencode/tera-core.manifest.json`.
11
+ * - Tracks ownership via `.opencode/tera.manifest.json`.
12
12
  * - Only updates files it owns, always keeping a backup first.
13
13
  * - Idempotent: re-running is safe; identical files are skipped.
14
14
  * - Never touches an existing user `opencode.json`.
@@ -30,7 +30,8 @@ import { verifyBundle, applyBundle } from "./lib/bundle.mjs";
30
30
 
31
31
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
32
32
  const PKG_ROOT = path.resolve(__dirname, "..");
33
- const MANIFEST_NAME = "tera-core.manifest.json";
33
+ const MANIFEST_NAME = "tera.manifest.json";
34
+ const LEGACY_MANIFEST_NAME = "tera-core.manifest.json";
34
35
  const CONFIG_EXAMPLE = "opencode.tera.example.json";
35
36
 
36
37
  /** Generate a stable device id from hostname + target path. */
@@ -78,7 +79,19 @@ function copyFile(src, dest) {
78
79
 
79
80
  function loadOwnership(target) {
80
81
  const p = path.join(target, ".opencode", MANIFEST_NAME);
81
- if (!fs.existsSync(p)) return { files: {} };
82
+ if (!fs.existsSync(p)) {
83
+ // Legacy name (pre-0.2.11) — migrate silently
84
+ const legacy = path.join(target, ".opencode", LEGACY_MANIFEST_NAME);
85
+ if (fs.existsSync(legacy)) {
86
+ try {
87
+ const parsed = JSON.parse(fs.readFileSync(legacy, "utf8"));
88
+ return { files: parsed.files && typeof parsed.files === "object" ? parsed.files : {} };
89
+ } catch {
90
+ return { files: {} };
91
+ }
92
+ }
93
+ return { files: {} };
94
+ }
82
95
  try {
83
96
  const parsed = JSON.parse(fs.readFileSync(p, "utf8"));
84
97
  return { files: parsed.files && typeof parsed.files === "object" ? parsed.files : {} };
@@ -197,7 +210,7 @@ function installProjectControl() {
197
210
 
198
211
  /* ----------------------------------- run ----------------------------------- */
199
212
 
200
- console.log(`\n@tera/core v${pkg.version} — install`);
213
+ console.log(`\nTera System Pro v${pkg.version} — install`);
201
214
  console.log(` target workspace : ${target}\n`);
202
215
 
203
216
  installGroup({ name: "agents", src: path.join(PKG_ROOT, "agents"), dest: path.join(target, ".opencode", "agents") });
@@ -231,6 +244,15 @@ const manifestPath = path.join(target, ".opencode", MANIFEST_NAME);
231
244
  fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
232
245
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
233
246
 
247
+ // Migrate away from the legacy manifest name (pre-0.2.11) — removes any "core" trace
248
+ const legacyManifestPath = path.join(target, ".opencode", LEGACY_MANIFEST_NAME);
249
+ if (fs.existsSync(legacyManifestPath)) {
250
+ try {
251
+ fs.unlinkSync(legacyManifestPath);
252
+ console.log(` [manifest] migrated: ${LEGACY_MANIFEST_NAME} → ${MANIFEST_NAME}`);
253
+ } catch { /* non-destructive best-effort */ }
254
+ }
255
+
234
256
  /* ------------------- license state (non-blocking, never breaks install) ------------------- */
235
257
 
236
258
  /**
@@ -423,16 +445,9 @@ async function tryServerSync() {
423
445
  console.log(" npm run tera-sync (or re-run install when online)");
424
446
  }
425
447
 
426
- // Step 2: integrity manifest (after sync, covers server files too)
427
- try {
428
- const integrityManifest = createIntegrityManifest(target);
429
- saveIntegrityManifest(target, integrityManifest);
430
- console.log(` [integrity] manifest created (${integrityManifest.fileCount} files tracked)`);
431
- } catch (err) {
432
- console.log(` [integrity] skipped (${err.message}) — install continues`);
433
- }
434
-
435
- // Step 3: license verification (API-first with email, offline fallback)
448
+ // Step 2: license verification (API-first with email, offline fallback)
449
+ // NOTE: license state is written BEFORE the integrity snapshot (SCP-2026-08-30-002)
450
+ // so the manifest always reflects the final on-disk state.
436
451
  const stateFile = path.join(target, ".tera", "license.state.json");
437
452
  const prevState = readLicenseState(stateFile) || {};
438
453
  let licenseKey = prevState.licenseKey;
@@ -441,7 +456,7 @@ async function tryServerSync() {
441
456
  if (!accountEmail) accountEmail = process.env.TERA_LICENSE_EMAIL || undefined;
442
457
  const deviceId = prevState.deviceId || createDeviceId(target);
443
458
 
444
- // 3a. best-effort activation (only when we have key + email and not activated yet)
459
+ // 2a. best-effort activation (only when we have key + email and not activated yet)
445
460
  if (licenseKey && accountEmail && !prevState.activated) {
446
461
  const act = await activateViaAPI(licenseKey, accountEmail, deviceId);
447
462
  if (act && act.ok === true) {
@@ -466,6 +481,15 @@ async function tryServerSync() {
466
481
 
467
482
  const sourceTag = result.source === "api" ? "☁️" : result.source === "offline" ? "🔒" : "🔄";
468
483
  console.log(` [license ] ${sourceTag} state=${result.state} (${result.reason})`);
484
+
485
+ // Step 3: integrity manifest (AFTER all writes — snapshot reflects final state)
486
+ try {
487
+ const integrityManifest = createIntegrityManifest(target);
488
+ saveIntegrityManifest(target, integrityManifest);
489
+ console.log(` [integrity] manifest created (${integrityManifest.fileCount} files tracked)`);
490
+ } catch (err) {
491
+ console.log(` [integrity] skipped (${err.message}) — install continues`);
492
+ }
469
493
  } catch (err) {
470
494
  console.log(` [license ] skipped (${err.message}) — install continues`);
471
495
  }
@@ -18,7 +18,7 @@ export function loadPublicKey() {
18
18
  * Returns { ok, reason, manifest?, contentSha256? }
19
19
  */
20
20
  export function verifyBundle(bundle) {
21
- if (!bundle || bundle.type !== "tera-core-bundle") {
21
+ if (!bundle || bundle.type !== "tera-bundle") {
22
22
  return { ok: false, reason: "not-a-bundle" };
23
23
  }
24
24
  if (typeof bundle.contentSha256 !== "string" || !bundle.signature) {
@@ -12,7 +12,9 @@ const CRITICAL_FILES = [
12
12
  "scripts/lib/integrity.mjs",
13
13
  "scripts/install.js",
14
14
  "scripts/preflight.mjs",
15
- ".tera/license.state.json",
15
+ // NOTE: .tera/license.state.json is intentionally NOT hashed (SCP-2026-08-30-002).
16
+ // It is a self-modifying STATE file (checkedAt written on every install/heartbeat/re-check).
17
+ // License security comes from the Ed25519-signed key, not from hashing the state file.
16
18
  ];
17
19
 
18
20
  /**
@@ -169,7 +169,7 @@ export async function enforceLicenseGate(target, options = {}) {
169
169
  message:
170
170
  "❌ Tera System files have been tampered with.\n" +
171
171
  `Tampered files: ${criticalCheck.tampered.map((t) => t.file).join(", ")}\n\n` +
172
- "Reinstall required: npm install @tera-system/core@latest",
172
+ "Reinstall required: npm install @tera-system/pro@latest (or npx tera-pro-restore)",
173
173
  };
174
174
  }
175
175
 
@@ -13,7 +13,10 @@ function usage() {
13
13
  tera-license gen --tier <basic|premium> --project <name> --expires <YYYY-MM-DD> [--seats N]
14
14
  (private key: env TERA_LICENSE_PRIVATE or the dev key under license/dev/)
15
15
  tera-license check [--key <licenseKey> | --state-file <path>]
16
- (or env TERA_LICENSE_KEY)`);
16
+ (or env TERA_LICENSE_KEY)
17
+ tera-license activate <licenseKey> [accountEmail]
18
+ (writes the key into .tera/license.state.json and verifies it)
19
+ (target: env TERA_TARGET_DIR / INIT_CWD / cwd)`);
17
20
  }
18
21
 
19
22
  const args = process.argv.slice(2);
@@ -58,6 +61,31 @@ if (cmd === "gen") {
58
61
  process.exit(0);
59
62
  }
60
63
  console.log(JSON.stringify(assessLicenseState(key), null, 2));
64
+ } else if (cmd === "activate") {
65
+ // Write a license key into .tera/license.state.json and verify it (SCP-2026-08-30-002).
66
+ const key = args[1] || flag("--key");
67
+ if (!key) { usage(); process.exit(1); }
68
+ const email = args[2] || flag("--email") || process.env.TERA_LICENSE_EMAIL || null;
69
+ const target = path.resolve(process.env.TERA_TARGET_DIR || process.env.INIT_CWD || process.cwd());
70
+ const stateFile = path.join(target, ".tera", "license.state.json");
71
+ fs.mkdirSync(path.dirname(stateFile), { recursive: true });
72
+ const prev = readLicenseState(stateFile) || {};
73
+ const assessment = assessLicenseState(key);
74
+ const state = {
75
+ ...prev,
76
+ state: assessment.state,
77
+ reason: assessment.reason,
78
+ licenseKey: key,
79
+ accountEmail: email || prev.accountEmail || null,
80
+ deviceId: prev.deviceId || null,
81
+ activated: true,
82
+ expiresAt: assessment.expiresAt || null,
83
+ graceUntil: assessment.graceUntil || null,
84
+ checkedAt: new Date().toISOString(),
85
+ };
86
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
87
+ console.log(`Activated: state=${assessment.state} (${assessment.reason}) — ${stateFile}`);
88
+ process.exit(assessment.state === "Active" || assessment.state === "Grace" ? 0 : 1);
61
89
  } else {
62
90
  usage();
63
91
  }