@tera-system/pro 0.2.11 → 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/MANIFEST.json +1 -1
- package/RELEASES.md +14 -0
- package/package.json +1 -1
- package/scripts/install.js +13 -11
- package/scripts/lib/integrity.mjs +3 -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.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"
|
package/RELEASES.md
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
|
|
7
7
|
|
|
8
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
|
+
|
|
9
23
|
## 0.2.11 (2026-08-30)
|
|
10
24
|
|
|
11
25
|
### 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.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",
|
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
|
/**
|
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
|
}
|