@tera-system/pro 0.2.11 → 0.2.13
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/MANIFEST.json +1 -1
- package/RELEASES.md +26 -0
- package/package.json +1 -1
- package/scripts/install.js +13 -11
- package/scripts/lib/integrity.mjs +3 -1
- package/scripts/lib/license-gate.mjs +1 -1
- package/scripts/tera-license.mjs +29 -1
package/MANIFEST.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tera-system/pro",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
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"
|
package/RELEASES.md
CHANGED
|
@@ -6,6 +6,32 @@
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## 0.2.13 (2026-08-30)
|
|
12
|
+
|
|
13
|
+
### Revocation speed: periodic recheck 30d -> 3d (SCP-2026-08-30-002)
|
|
14
|
+
|
|
15
|
+
**What changed (scripts only):**
|
|
16
|
+
- license-gate.mjs: RECHECK_INTERVAL_DAYS 30 -> 3 - an active client now contacts the license server at least every 3 days, so a server-side revocation is detected within <=3 days of usage (instead of up to 30).
|
|
17
|
+
|
|
18
|
+
**Why:**
|
|
19
|
+
- Revocation of Saeed was live on the server, but the 30-day recheck interval meant an active client would not notice for up to 30 days. 3 days balances detection speed with server load.
|
|
20
|
+
- Offline at recheck -> allowed (grace), no false blocking.
|
|
21
|
+
|
|
22
|
+
## 0.2.12 (2026-08-30)
|
|
23
|
+
|
|
24
|
+
### Bug fix: preflight false tamper detection (SCP-2026-08-30-002)
|
|
25
|
+
|
|
26
|
+
**What changed (scripts only - bundle unchanged):**
|
|
27
|
+
- 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.
|
|
28
|
+
- install.js: license state is now written BEFORE the integrity snapshot, so the manifest always reflects the final on-disk state.
|
|
29
|
+
- tera-license.mjs: added the 'activate <key> [email]' command (TERA_LICENSE_GUARD.md instructs blocked users to run it - it did not exist).
|
|
30
|
+
|
|
31
|
+
**Why:**
|
|
32
|
+
- 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.
|
|
33
|
+
- No bundle change: the fix is entirely in npm-shipped scripts. Server bundle stays v0.2.11.
|
|
34
|
+
|
|
9
35
|
## 0.2.11 (2026-08-30)
|
|
10
36
|
|
|
11
37
|
### Security hardening: license-gate message fix + zero core traces
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tera-system/pro",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
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",
|
package/scripts/install.js
CHANGED
|
@@ -445,16 +445,9 @@ async function tryServerSync() {
|
|
|
445
445
|
console.log(" npm run tera-sync (or re-run install when online)");
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
// Step 2:
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
saveIntegrityManifest(target, integrityManifest);
|
|
452
|
-
console.log(` [integrity] manifest created (${integrityManifest.fileCount} files tracked)`);
|
|
453
|
-
} catch (err) {
|
|
454
|
-
console.log(` [integrity] skipped (${err.message}) — install continues`);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// 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.
|
|
458
451
|
const stateFile = path.join(target, ".tera", "license.state.json");
|
|
459
452
|
const prevState = readLicenseState(stateFile) || {};
|
|
460
453
|
let licenseKey = prevState.licenseKey;
|
|
@@ -463,7 +456,7 @@ async function tryServerSync() {
|
|
|
463
456
|
if (!accountEmail) accountEmail = process.env.TERA_LICENSE_EMAIL || undefined;
|
|
464
457
|
const deviceId = prevState.deviceId || createDeviceId(target);
|
|
465
458
|
|
|
466
|
-
//
|
|
459
|
+
// 2a. best-effort activation (only when we have key + email and not activated yet)
|
|
467
460
|
if (licenseKey && accountEmail && !prevState.activated) {
|
|
468
461
|
const act = await activateViaAPI(licenseKey, accountEmail, deviceId);
|
|
469
462
|
if (act && act.ok === true) {
|
|
@@ -488,6 +481,15 @@ async function tryServerSync() {
|
|
|
488
481
|
|
|
489
482
|
const sourceTag = result.source === "api" ? "☁️" : result.source === "offline" ? "🔒" : "🔄";
|
|
490
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
|
+
}
|
|
491
493
|
} catch (err) {
|
|
492
494
|
console.log(` [license ] skipped (${err.message}) — install continues`);
|
|
493
495
|
}
|
|
@@ -12,7 +12,9 @@ const CRITICAL_FILES = [
|
|
|
12
12
|
"scripts/lib/integrity.mjs",
|
|
13
13
|
"scripts/install.js",
|
|
14
14
|
"scripts/preflight.mjs",
|
|
15
|
-
|
|
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
|
/**
|
|
@@ -15,7 +15,7 @@ export const GATE_RESULTS = {
|
|
|
15
15
|
DENY_NEEDS_RECHECK: "deny-needs-recheck",
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const RECHECK_INTERVAL_DAYS = 30
|
|
18
|
+
const RECHECK_INTERVAL_DAYS = 3; // SCP-2026-08-30-002: revocation detection within <=3 days (was 30)
|
|
19
19
|
const RECHECK_INTERVAL_MS = RECHECK_INTERVAL_DAYS * 86400000;
|
|
20
20
|
|
|
21
21
|
/**
|
package/scripts/tera-license.mjs
CHANGED
|
@@ -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
|
}
|