instar 1.1.2 → 1.1.4
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 +270 -1
- package/dist/cli.js +26 -3
- package/dist/cli.js.map +1 -1
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +23 -1
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +15 -0
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +107 -8
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/lifeline/rateLimitState.d.ts.map +1 -1
- package/dist/lifeline/rateLimitState.js +8 -1
- package/dist/lifeline/rateLimitState.js.map +1 -1
- package/dist/memory/NativeModuleHealer.d.ts.map +1 -1
- package/dist/memory/NativeModuleHealer.js +8 -1
- package/dist/memory/NativeModuleHealer.js.map +1 -1
- package/package.json +1 -1
- package/scripts/check-repo-invariants.mjs +68 -0
- package/scripts/pre-push-fixture-guard.mjs +155 -0
- package/src/data/builtin-manifest.json +32 -32
- package/upgrades/1.1.3.md +124 -0
- package/upgrades/1.1.4.md +42 -0
- package/upgrades/side-effects/lifeline-version-skew-recovery.md +180 -0
- package/upgrades/side-effects/test-env-isolation.md +84 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// safe-git-allow: pre-push critical path — must stay lightweight, no SafeGitExecutor audit-log writes from the hook.
|
|
3
|
+
/**
|
|
4
|
+
* Pre-push guard: reject test-fixture commits before they ship.
|
|
5
|
+
*
|
|
6
|
+
* Git invokes pre-push hooks with GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE
|
|
7
|
+
* set in the env. When the hook runs `npm run test:smoke`, the test
|
|
8
|
+
* suite inherits those overrides, and any test that spawns `git init`
|
|
9
|
+
* + `git commit` in a tmpdir without sanitizing env ends up committing
|
|
10
|
+
* into the real repo on whichever branch is currently checked out.
|
|
11
|
+
*
|
|
12
|
+
* That's how PRs #130 and #277 picked up "Initial commit" / "seed" /
|
|
13
|
+
* "Worktree commit N" stowaways that overwrote README.md on main.
|
|
14
|
+
*
|
|
15
|
+
* Layer 1 (vitest-setup.ts strip) closes the root cause. This guard is
|
|
16
|
+
* the second line of defense: even if a future test reintroduces a raw
|
|
17
|
+
* git call without sanitizing env, the resulting commit can never reach
|
|
18
|
+
* the remote because this hook refuses to push it.
|
|
19
|
+
*
|
|
20
|
+
* Behavior:
|
|
21
|
+
* - Scans commits on HEAD that aren't yet on origin/main.
|
|
22
|
+
* - Fails the push if any of them match the fixture-pollution signature
|
|
23
|
+
* (author email test@instar.local / t@t.com / t@e.com, or commit
|
|
24
|
+
* messages "Initial commit" / "seed" / "Worktree commit <N>").
|
|
25
|
+
* - Prints what it found so the author can `git reset` past the bad
|
|
26
|
+
* commits and re-push.
|
|
27
|
+
*
|
|
28
|
+
* Escape hatch (for legitimate fixture commits in test-only branches
|
|
29
|
+
* that will never merge): INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1
|
|
30
|
+
*
|
|
31
|
+
* Exit codes: 0 = clean (or skipped), 1 = pollution detected.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { execFileSync } from 'node:child_process';
|
|
35
|
+
|
|
36
|
+
const SKIP = process.env.INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP === '1';
|
|
37
|
+
if (SKIP) {
|
|
38
|
+
console.log('⏭️ INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1 — fixture guard bypassed.');
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Strip the same env overrides we accuse of causing the bug. Otherwise
|
|
43
|
+
// our own `git log` here would be subject to the same redirection.
|
|
44
|
+
const env = { ...process.env };
|
|
45
|
+
delete env.GIT_DIR;
|
|
46
|
+
delete env.GIT_WORK_TREE;
|
|
47
|
+
delete env.GIT_INDEX_FILE;
|
|
48
|
+
delete env.GIT_OBJECT_DIRECTORY;
|
|
49
|
+
delete env.GIT_COMMON_DIR;
|
|
50
|
+
|
|
51
|
+
function git(args) {
|
|
52
|
+
return execFileSync('git', args, { encoding: 'utf-8', env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let baseRef;
|
|
56
|
+
try {
|
|
57
|
+
// Prefer upstream tracking branch if one is configured.
|
|
58
|
+
baseRef = git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}']).trim();
|
|
59
|
+
} catch {
|
|
60
|
+
// No upstream — fall back to whichever remote main we can resolve.
|
|
61
|
+
for (const candidate of ['upstream/main', 'origin/main']) {
|
|
62
|
+
try {
|
|
63
|
+
git(['rev-parse', '--verify', '--quiet', candidate]);
|
|
64
|
+
baseRef = candidate;
|
|
65
|
+
break;
|
|
66
|
+
} catch { /* try next */ }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!baseRef) {
|
|
71
|
+
// No remote main to compare against — likely a fresh repo. Skip silently.
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// `<base>..HEAD` lists commits on HEAD that aren't on base. NUL-separated
|
|
76
|
+
// records of "<sha>\t<author_email>\t<author_name>\t<subject>" — using \0
|
|
77
|
+
// between records lets us survive commit messages that contain newlines.
|
|
78
|
+
let log;
|
|
79
|
+
try {
|
|
80
|
+
log = git([
|
|
81
|
+
'log',
|
|
82
|
+
`${baseRef}..HEAD`,
|
|
83
|
+
'--pretty=format:%H%x09%ae%x09%an%x09%s%x00',
|
|
84
|
+
]);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// Couldn't compute the range. Don't block the push — a real failure
|
|
87
|
+
// would surface elsewhere; we'd rather miss a guard than wedge pushes
|
|
88
|
+
// on every fresh repo / unborn branch.
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const records = log.split('\0').map((r) => r.trim()).filter(Boolean);
|
|
93
|
+
|
|
94
|
+
const BAD_EMAILS = new Set([
|
|
95
|
+
'test@instar.local',
|
|
96
|
+
't@t.com',
|
|
97
|
+
't@e.com',
|
|
98
|
+
'test@test.com',
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const BAD_SUBJECT_PATTERNS = [
|
|
102
|
+
/^Initial commit$/,
|
|
103
|
+
/^seed$/,
|
|
104
|
+
/^Worktree commit \d+$/,
|
|
105
|
+
/^init$/,
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
const violations = [];
|
|
109
|
+
for (const record of records) {
|
|
110
|
+
const [sha, email, name, subject] = record.split('\t');
|
|
111
|
+
const reasons = [];
|
|
112
|
+
if (BAD_EMAILS.has(email)) {
|
|
113
|
+
reasons.push(`fixture-identity author <${email}>`);
|
|
114
|
+
}
|
|
115
|
+
for (const pattern of BAD_SUBJECT_PATTERNS) {
|
|
116
|
+
if (pattern.test(subject)) {
|
|
117
|
+
reasons.push(`fixture commit message "${subject}"`);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (reasons.length > 0) {
|
|
122
|
+
violations.push({ sha: sha.slice(0, 10), name, email, subject, reasons });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (violations.length === 0) {
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const lines = [];
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push('🚫 pre-push-fixture-guard: refusing to push test-fixture commits.');
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push('These commits look like test-fixture pollution (test wrote into');
|
|
135
|
+
lines.push('the real repo because GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE');
|
|
136
|
+
lines.push('leaked from this pre-push hook into a child test process):');
|
|
137
|
+
lines.push('');
|
|
138
|
+
for (const v of violations) {
|
|
139
|
+
lines.push(` ${v.sha} ${v.name} <${v.email}> "${v.subject}"`);
|
|
140
|
+
for (const r of v.reasons) {
|
|
141
|
+
lines.push(` → ${r}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
lines.push('');
|
|
145
|
+
lines.push('How to recover:');
|
|
146
|
+
lines.push(` 1. Inspect the commits: git log ${baseRef}..HEAD`);
|
|
147
|
+
lines.push(` 2. Drop them: git reset --hard <last good sha>`);
|
|
148
|
+
lines.push(' 3. Force-push the cleaned branch.');
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push('If this is intentional (e.g. a test-only branch you do not plan');
|
|
151
|
+
lines.push('to merge), bypass with INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1.');
|
|
152
|
+
lines.push('');
|
|
153
|
+
|
|
154
|
+
process.stderr.write(lines.join('\n'));
|
|
155
|
+
process.exit(1);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-05-
|
|
5
|
-
"instarVersion": "1.1.
|
|
4
|
+
"generatedAt": "2026-05-21T04:11:40.504Z",
|
|
5
|
+
"instarVersion": "1.1.4",
|
|
6
6
|
"entryCount": 190,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -752,7 +752,7 @@
|
|
|
752
752
|
"type": "cli-command",
|
|
753
753
|
"domain": "setup",
|
|
754
754
|
"sourcePath": "src/cli.ts",
|
|
755
|
-
"contentHash": "
|
|
755
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
756
756
|
"since": "2025-01-01"
|
|
757
757
|
},
|
|
758
758
|
"cli:setup": {
|
|
@@ -760,7 +760,7 @@
|
|
|
760
760
|
"type": "cli-command",
|
|
761
761
|
"domain": "setup",
|
|
762
762
|
"sourcePath": "src/cli.ts",
|
|
763
|
-
"contentHash": "
|
|
763
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
764
764
|
"since": "2025-01-01"
|
|
765
765
|
},
|
|
766
766
|
"cli:add": {
|
|
@@ -768,7 +768,7 @@
|
|
|
768
768
|
"type": "cli-command",
|
|
769
769
|
"domain": "setup",
|
|
770
770
|
"sourcePath": "src/cli.ts",
|
|
771
|
-
"contentHash": "
|
|
771
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
772
772
|
"since": "2025-01-01"
|
|
773
773
|
},
|
|
774
774
|
"cli:backup": {
|
|
@@ -776,7 +776,7 @@
|
|
|
776
776
|
"type": "cli-command",
|
|
777
777
|
"domain": "operations",
|
|
778
778
|
"sourcePath": "src/cli.ts",
|
|
779
|
-
"contentHash": "
|
|
779
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
780
780
|
"since": "2025-01-01"
|
|
781
781
|
},
|
|
782
782
|
"cli:git": {
|
|
@@ -784,7 +784,7 @@
|
|
|
784
784
|
"type": "cli-command",
|
|
785
785
|
"domain": "coordination",
|
|
786
786
|
"sourcePath": "src/cli.ts",
|
|
787
|
-
"contentHash": "
|
|
787
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
788
788
|
"since": "2025-01-01"
|
|
789
789
|
},
|
|
790
790
|
"cli:memory": {
|
|
@@ -792,7 +792,7 @@
|
|
|
792
792
|
"type": "cli-command",
|
|
793
793
|
"domain": "memory",
|
|
794
794
|
"sourcePath": "src/cli.ts",
|
|
795
|
-
"contentHash": "
|
|
795
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
796
796
|
"since": "2025-01-01"
|
|
797
797
|
},
|
|
798
798
|
"cli:knowledge": {
|
|
@@ -800,7 +800,7 @@
|
|
|
800
800
|
"type": "cli-command",
|
|
801
801
|
"domain": "memory",
|
|
802
802
|
"sourcePath": "src/cli.ts",
|
|
803
|
-
"contentHash": "
|
|
803
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
804
804
|
"since": "2025-01-01"
|
|
805
805
|
},
|
|
806
806
|
"cli:semantic": {
|
|
@@ -808,7 +808,7 @@
|
|
|
808
808
|
"type": "cli-command",
|
|
809
809
|
"domain": "memory",
|
|
810
810
|
"sourcePath": "src/cli.ts",
|
|
811
|
-
"contentHash": "
|
|
811
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
812
812
|
"since": "2025-01-01"
|
|
813
813
|
},
|
|
814
814
|
"cli:intent": {
|
|
@@ -816,7 +816,7 @@
|
|
|
816
816
|
"type": "cli-command",
|
|
817
817
|
"domain": "intent",
|
|
818
818
|
"sourcePath": "src/cli.ts",
|
|
819
|
-
"contentHash": "
|
|
819
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
820
820
|
"since": "2025-01-01"
|
|
821
821
|
},
|
|
822
822
|
"cli:feedback": {
|
|
@@ -824,7 +824,7 @@
|
|
|
824
824
|
"type": "cli-command",
|
|
825
825
|
"domain": "feedback",
|
|
826
826
|
"sourcePath": "src/cli.ts",
|
|
827
|
-
"contentHash": "
|
|
827
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
828
828
|
"since": "2025-01-01"
|
|
829
829
|
},
|
|
830
830
|
"cli:server": {
|
|
@@ -832,7 +832,7 @@
|
|
|
832
832
|
"type": "cli-command",
|
|
833
833
|
"domain": "server",
|
|
834
834
|
"sourcePath": "src/cli.ts",
|
|
835
|
-
"contentHash": "
|
|
835
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
836
836
|
"since": "2025-01-01"
|
|
837
837
|
},
|
|
838
838
|
"cli:status": {
|
|
@@ -840,7 +840,7 @@
|
|
|
840
840
|
"type": "cli-command",
|
|
841
841
|
"domain": "monitoring",
|
|
842
842
|
"sourcePath": "src/cli.ts",
|
|
843
|
-
"contentHash": "
|
|
843
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
844
844
|
"since": "2025-01-01"
|
|
845
845
|
},
|
|
846
846
|
"cli:user": {
|
|
@@ -848,7 +848,7 @@
|
|
|
848
848
|
"type": "cli-command",
|
|
849
849
|
"domain": "users",
|
|
850
850
|
"sourcePath": "src/cli.ts",
|
|
851
|
-
"contentHash": "
|
|
851
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
852
852
|
"since": "2025-01-01"
|
|
853
853
|
},
|
|
854
854
|
"cli:relationship": {
|
|
@@ -856,7 +856,7 @@
|
|
|
856
856
|
"type": "cli-command",
|
|
857
857
|
"domain": "relationships",
|
|
858
858
|
"sourcePath": "src/cli.ts",
|
|
859
|
-
"contentHash": "
|
|
859
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
860
860
|
"since": "2025-01-01"
|
|
861
861
|
},
|
|
862
862
|
"cli:job": {
|
|
@@ -864,7 +864,7 @@
|
|
|
864
864
|
"type": "cli-command",
|
|
865
865
|
"domain": "scheduling",
|
|
866
866
|
"sourcePath": "src/cli.ts",
|
|
867
|
-
"contentHash": "
|
|
867
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
868
868
|
"since": "2025-01-01"
|
|
869
869
|
},
|
|
870
870
|
"cli:lifeline": {
|
|
@@ -872,7 +872,7 @@
|
|
|
872
872
|
"type": "cli-command",
|
|
873
873
|
"domain": "communication",
|
|
874
874
|
"sourcePath": "src/cli.ts",
|
|
875
|
-
"contentHash": "
|
|
875
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
876
876
|
"since": "2025-01-01"
|
|
877
877
|
},
|
|
878
878
|
"cli:list": {
|
|
@@ -880,7 +880,7 @@
|
|
|
880
880
|
"type": "cli-command",
|
|
881
881
|
"domain": "monitoring",
|
|
882
882
|
"sourcePath": "src/cli.ts",
|
|
883
|
-
"contentHash": "
|
|
883
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
884
884
|
"since": "2025-01-01"
|
|
885
885
|
},
|
|
886
886
|
"cli:autostart": {
|
|
@@ -888,7 +888,7 @@
|
|
|
888
888
|
"type": "cli-command",
|
|
889
889
|
"domain": "operations",
|
|
890
890
|
"sourcePath": "src/cli.ts",
|
|
891
|
-
"contentHash": "
|
|
891
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
892
892
|
"since": "2025-01-01"
|
|
893
893
|
},
|
|
894
894
|
"cli:migrate": {
|
|
@@ -896,7 +896,7 @@
|
|
|
896
896
|
"type": "cli-command",
|
|
897
897
|
"domain": "updates",
|
|
898
898
|
"sourcePath": "src/cli.ts",
|
|
899
|
-
"contentHash": "
|
|
899
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
900
900
|
"since": "2025-01-01"
|
|
901
901
|
},
|
|
902
902
|
"cli:machines": {
|
|
@@ -904,7 +904,7 @@
|
|
|
904
904
|
"type": "cli-command",
|
|
905
905
|
"domain": "coordination",
|
|
906
906
|
"sourcePath": "src/cli.ts",
|
|
907
|
-
"contentHash": "
|
|
907
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
908
908
|
"since": "2025-01-01"
|
|
909
909
|
},
|
|
910
910
|
"cli:whoami": {
|
|
@@ -912,7 +912,7 @@
|
|
|
912
912
|
"type": "cli-command",
|
|
913
913
|
"domain": "identity",
|
|
914
914
|
"sourcePath": "src/cli.ts",
|
|
915
|
-
"contentHash": "
|
|
915
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
916
916
|
"since": "2025-01-01"
|
|
917
917
|
},
|
|
918
918
|
"cli:pair": {
|
|
@@ -920,7 +920,7 @@
|
|
|
920
920
|
"type": "cli-command",
|
|
921
921
|
"domain": "coordination",
|
|
922
922
|
"sourcePath": "src/cli.ts",
|
|
923
|
-
"contentHash": "
|
|
923
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
924
924
|
"since": "2025-01-01"
|
|
925
925
|
},
|
|
926
926
|
"cli:join": {
|
|
@@ -928,7 +928,7 @@
|
|
|
928
928
|
"type": "cli-command",
|
|
929
929
|
"domain": "coordination",
|
|
930
930
|
"sourcePath": "src/cli.ts",
|
|
931
|
-
"contentHash": "
|
|
931
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
932
932
|
"since": "2025-01-01"
|
|
933
933
|
},
|
|
934
934
|
"cli:wakeup": {
|
|
@@ -936,7 +936,7 @@
|
|
|
936
936
|
"type": "cli-command",
|
|
937
937
|
"domain": "coordination",
|
|
938
938
|
"sourcePath": "src/cli.ts",
|
|
939
|
-
"contentHash": "
|
|
939
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
940
940
|
"since": "2025-01-01"
|
|
941
941
|
},
|
|
942
942
|
"cli:leave": {
|
|
@@ -944,7 +944,7 @@
|
|
|
944
944
|
"type": "cli-command",
|
|
945
945
|
"domain": "coordination",
|
|
946
946
|
"sourcePath": "src/cli.ts",
|
|
947
|
-
"contentHash": "
|
|
947
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
948
948
|
"since": "2025-01-01"
|
|
949
949
|
},
|
|
950
950
|
"cli:doctor": {
|
|
@@ -952,7 +952,7 @@
|
|
|
952
952
|
"type": "cli-command",
|
|
953
953
|
"domain": "monitoring",
|
|
954
954
|
"sourcePath": "src/cli.ts",
|
|
955
|
-
"contentHash": "
|
|
955
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
956
956
|
"since": "2025-01-01"
|
|
957
957
|
},
|
|
958
958
|
"cli:review": {
|
|
@@ -960,7 +960,7 @@
|
|
|
960
960
|
"type": "cli-command",
|
|
961
961
|
"domain": "monitoring",
|
|
962
962
|
"sourcePath": "src/cli.ts",
|
|
963
|
-
"contentHash": "
|
|
963
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
964
964
|
"since": "2025-01-01"
|
|
965
965
|
},
|
|
966
966
|
"cli:nuke": {
|
|
@@ -968,7 +968,7 @@
|
|
|
968
968
|
"type": "cli-command",
|
|
969
969
|
"domain": "operations",
|
|
970
970
|
"sourcePath": "src/cli.ts",
|
|
971
|
-
"contentHash": "
|
|
971
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
972
972
|
"since": "2025-01-01"
|
|
973
973
|
},
|
|
974
974
|
"cli:playbook": {
|
|
@@ -976,7 +976,7 @@
|
|
|
976
976
|
"type": "cli-command",
|
|
977
977
|
"domain": "context",
|
|
978
978
|
"sourcePath": "src/cli.ts",
|
|
979
|
-
"contentHash": "
|
|
979
|
+
"contentHash": "ef620787cb2d40a51831b00ba7071e67aa0d4cac86027dc3a04635d344608d29",
|
|
980
980
|
"since": "2025-01-01"
|
|
981
981
|
},
|
|
982
982
|
"template:build-stop-hook.sh": {
|
|
@@ -1496,7 +1496,7 @@
|
|
|
1496
1496
|
"type": "subsystem",
|
|
1497
1497
|
"domain": "communication",
|
|
1498
1498
|
"sourcePath": "src/lifeline/TelegramLifeline.ts",
|
|
1499
|
-
"contentHash": "
|
|
1499
|
+
"contentHash": "45cd2b449e34134cae67859e79e378ccc131f481b4d5701faf26913b8a3a26e2",
|
|
1500
1500
|
"since": "2025-01-01"
|
|
1501
1501
|
},
|
|
1502
1502
|
"subsystem:orphan-process-reaper": {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
<!-- patch = bug fixes, refactors, test additions, doc updates -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
**fix(lifeline): close the silent-drop version-skew failure class.**
|
|
9
|
+
|
|
10
|
+
Five interlocking fixes that together address the 2026-05-19 →
|
|
11
|
+
2026-05-20 b2lead-insights field incident (21h silent Telegram ingress
|
|
12
|
+
drop after a server auto-update from v1.0.13 → v1.1.0 left the
|
|
13
|
+
lifeline on v1.0.13; server's `/internal/telegram-forward` rejected
|
|
14
|
+
the lifeline with HTTP 426 and the lifeline's drop-after-3-failures
|
|
15
|
+
replay policy silently lost user messages).
|
|
16
|
+
|
|
17
|
+
1. **`rateLimitState.decide` — `versionSkew` bucket bypasses
|
|
18
|
+
cooldown.** A hard incompatibility cannot be cured by waiting; the
|
|
19
|
+
per-minute cooldown wedged every restart attempt. The daily cap
|
|
20
|
+
(3 per 24h) and storm detection remain as backstops.
|
|
21
|
+
|
|
22
|
+
2. **Replay-loop drop policy is gated on a version-skew episode
|
|
23
|
+
flag.** When `forwardToServer` raises `ForwardVersionSkewError`,
|
|
24
|
+
the lifeline sets `versionSkewActive = true` and sends one
|
|
25
|
+
user-visible Telegram alert ("ingress paused: version skew
|
|
26
|
+
detected, your messages are not lost"). The replay loop checks
|
|
27
|
+
the flag BEFORE the `MAX_REPLAY_FAILURES` drop check and re-queues
|
|
28
|
+
without incrementing the failure counter. Cleared on the next
|
|
29
|
+
successful forward.
|
|
30
|
+
|
|
31
|
+
3. **`instar lifeline restart` CLI uses the correct service label.**
|
|
32
|
+
Was `com.instar.<projectName>.lifeline` — different domain AND
|
|
33
|
+
different suffix from what `installMacOSLaunchAgent` writes
|
|
34
|
+
(`ai.instar.<projectName>`). `launchctl kickstart` always failed,
|
|
35
|
+
silently fell back to pkill.
|
|
36
|
+
|
|
37
|
+
4. **pkill fallback escalates to SIGKILL; lock-acquire recognizes
|
|
38
|
+
wedged sleeping processes.** SIGTERM, 3s grace, SIGKILL by name
|
|
39
|
+
pattern in the CLI. In `acquireLockFile`, 'S' (sleeping) state
|
|
40
|
+
with lock-write age > 5 min is treated as a wedged lifeline:
|
|
41
|
+
SIGTERM → 3s poll → SIGKILL → take the lock.
|
|
42
|
+
|
|
43
|
+
5. **`npm rebuild` for better-sqlite3 uses `--build-from-source` and
|
|
44
|
+
`--ignore-scripts`.** Two call sites: `ServerSupervisor`
|
|
45
|
+
preflight rebuild and `NativeModuleHealer.healBetterSqlite3Sync`.
|
|
46
|
+
Without `--build-from-source`, npm can install a cached prebuilt
|
|
47
|
+
with the same wrong ABI and exit 0 — producing the "rebuild
|
|
48
|
+
succeeded but module still fails to load" pattern in the b2lead
|
|
49
|
+
logs.
|
|
50
|
+
|
|
51
|
+
Files touched (excluding tests / docs / upgrade notes):
|
|
52
|
+
- `src/lifeline/rateLimitState.ts` — `decide()`
|
|
53
|
+
- `src/lifeline/TelegramLifeline.ts` — instance state,
|
|
54
|
+
`handleVersionSkew`, `forwardToServer`, `replayQueue`,
|
|
55
|
+
`acquireLockFile`
|
|
56
|
+
- `src/cli.ts` — `lifelineCmd.command('restart')`
|
|
57
|
+
- `src/lifeline/ServerSupervisor.ts` — preflight rebuild argv
|
|
58
|
+
- `src/memory/NativeModuleHealer.ts` — in-line rebuild argv
|
|
59
|
+
|
|
60
|
+
## Evidence
|
|
61
|
+
|
|
62
|
+
**Repro of the original failure** (b2lead-insights, 2026-05-19 evening
|
|
63
|
+
→ 2026-05-20 ~17:50 UTC):
|
|
64
|
+
|
|
65
|
+
1. Server auto-updated v1.0.13 → v1.1.0.
|
|
66
|
+
2. Lifeline kept running v1.0.13.
|
|
67
|
+
3. User Telegram messages → lifeline forwards to server → server
|
|
68
|
+
returns HTTP 426 with `{ok: false, upgradeRequired: true,
|
|
69
|
+
serverVersion: '1.1.0', action: 'restart', reason: 'major-minor-mismatch'}`.
|
|
70
|
+
4. Lifeline catches `ForwardVersionSkewError`, calls
|
|
71
|
+
`handleVersionSkew` → `initiateRestart('versionSkew', ...)`.
|
|
72
|
+
5. `RestartOrchestrator.maybeRestart` consults `rateLimitState.decide`.
|
|
73
|
+
`elapsed < WATCHDOG_COOLDOWN_MS` → returns `{allowed: false, reason:
|
|
74
|
+
'cooldown-active'}`. No restart.
|
|
75
|
+
6. Lifeline returns false from `forwardToServer`. Replay loop
|
|
76
|
+
increments `replayFailures`. After 3 attempts → drop.
|
|
77
|
+
7. 40+ "server-down" notifications + at least 2 explicitly-logged
|
|
78
|
+
dropped messages (`tg-6577`, `tg-6580`). No user-visible alert.
|
|
79
|
+
|
|
80
|
+
**Observed before:** `rateLimitState.ts:112` had no special-case for
|
|
81
|
+
`versionSkew` — the cooldown check fired for all buckets. The replay
|
|
82
|
+
loop at `TelegramLifeline.ts:1394` checked `failures >=
|
|
83
|
+
MAX_REPLAY_FAILURES` unconditionally. The CLI restart command at
|
|
84
|
+
`cli.ts:1639` hardcoded `com.instar.${projectName}.lifeline`.
|
|
85
|
+
`ServerSupervisor.ts:751` and `NativeModuleHealer.ts:397` both ran
|
|
86
|
+
`npm rebuild better-sqlite3` without `--build-from-source`.
|
|
87
|
+
|
|
88
|
+
**Observed after:**
|
|
89
|
+
- 26 new + extended unit tests (8 new in
|
|
90
|
+
`tests/unit/lifeline/version-skew-recovery.test.ts`, 1 new in
|
|
91
|
+
`tests/unit/lifeline/rateLimitState.test.ts`) cover all five paths.
|
|
92
|
+
- Source assertions verify the supervisor rebuild argv includes
|
|
93
|
+
`--build-from-source` AND `--ignore-scripts`, the healer in-line
|
|
94
|
+
rebuild includes both flags, the CLI restart command builds
|
|
95
|
+
`ai.instar.${...}` and never `com.instar.${...}.lifeline`, the
|
|
96
|
+
pkill fallback contains both `-TERM` and `-KILL`, the
|
|
97
|
+
`versionSkewActive` flag is set in `handleVersionSkew`, cleared in
|
|
98
|
+
`forwardToServer` success, checked in `replayQueue` BEFORE the
|
|
99
|
+
drop branch, and the lock-acquire path recognizes wedged-S.
|
|
100
|
+
|
|
101
|
+
Spec: `docs/specs/lifeline-version-skew-recovery.md`
|
|
102
|
+
ELI16: `docs/specs/lifeline-version-skew-recovery.eli16.md`
|
|
103
|
+
Side-effects review: `upgrades/side-effects/lifeline-version-skew-recovery.md`
|
|
104
|
+
|
|
105
|
+
## What to Tell Your User
|
|
106
|
+
|
|
107
|
+
- **You will never silently lose a message during an auto-update
|
|
108
|
+
again**: if my server and my Telegram lifeline temporarily get out
|
|
109
|
+
of sync after an update, I'll send you one clear note that ingress
|
|
110
|
+
is paused, keep all your messages safely in the queue, and replay
|
|
111
|
+
them as soon as the lifeline catches up.
|
|
112
|
+
- **Recovery is automatic**: the bug class that needed a manual
|
|
113
|
+
restart to clear is now self-healing. No operator intervention.
|
|
114
|
+
|
|
115
|
+
## Summary of New Capabilities
|
|
116
|
+
|
|
117
|
+
| Capability | How to Use |
|
|
118
|
+
|-----------|-----------|
|
|
119
|
+
| Version-skew cooldown bypass | automatic |
|
|
120
|
+
| Version-skew user alert (one per episode) | automatic |
|
|
121
|
+
| No-drop policy during version-skew episodes | automatic |
|
|
122
|
+
| CLI lifeline restart resolves correct service | automatic via `instar lifeline restart` |
|
|
123
|
+
| Wedged-lifeline lock recovery | automatic |
|
|
124
|
+
| Force-from-source native rebuilds | automatic |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- bump: patch -->
|
|
4
|
+
<!-- patch = bug fixes, refactors, test additions, doc updates -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
**fix(test-env): close the inherited-GIT_DIR failure class.**
|
|
9
|
+
|
|
10
|
+
When git invokes a hook (`.husky/pre-push` runs `npm run test:smoke`), it sets `GIT_DIR` / `GIT_WORK_TREE` / `GIT_INDEX_FILE` / `GIT_OBJECT_DIRECTORY` / `GIT_COMMON_DIR` on the child env. Those variables override cwd-based repo resolution for every descendant git process, so test fixtures that did `git init && git commit` in a tmpdir actually committed into the parent repo on whichever branch was checked out. That's what landed `# Test Project` over the project's `README.md` on `main` via PRs #130 and #277 — the README clobber shipped to npm in the v1.1.x series before PR #285 restored it.
|
|
11
|
+
|
|
12
|
+
This release closes the failure class in five interlocking layers:
|
|
13
|
+
|
|
14
|
+
1. **`tests/vitest-setup.ts` strips the GIT_DIR family** from `process.env` before any test file loads. Single-line root-cause fix — no test in the suite can inherit the dangerous overrides from the parent pre-push environment.
|
|
15
|
+
|
|
16
|
+
2. **`tests/helpers/git-test-env.ts` exports `sanitizedGitEnv()`** for fixtures that pass their own `env` object to `spawnSync` / `execFileSync`. Applied to the three fixtures whose write patterns are visible in PR #130's diff (`worktree-monitor.test.ts`, `SafeGitExecutor.test.ts`, `rich-profile-integration.test.ts`). Defense-in-depth on top of Layer 1.
|
|
17
|
+
|
|
18
|
+
3. **`scripts/pre-push-fixture-guard.mjs`** runs in `.husky/pre-push` before the smoke tests and refuses pushes whose ahead-of-upstream commits carry the historical fixture signature — author email in `{test@instar.local, t@t.com, t@e.com, test@test.com}` or subject matching `Initial commit` / `seed` / `init` / `Worktree commit N`. Bypass for legitimate test-only branches: `INSTAR_PRE_PUSH_FIXTURE_GUARD_SKIP=1`.
|
|
19
|
+
|
|
20
|
+
4. **`scripts/check-repo-invariants.mjs`** runs as a dedicated `Repo Invariants` CI job and fails the build if `README.md` drops below 100 lines or if `file-0.txt` / `seed` appear at repo root. Final structural safety net; threshold overridable via `INSTAR_README_MIN_LINES`.
|
|
21
|
+
|
|
22
|
+
5. **Removed `file-0.txt` and `seed`** stowaway files from repo root — fixture droppings from prior incidents that would have falsely passed Layer 4 on the PR adding it.
|
|
23
|
+
|
|
24
|
+
Spec: `docs/specs/test-env-isolation.md`. ELI16: `docs/specs/test-env-isolation.eli16.md`. Side-effects review: `upgrades/side-effects/test-env-isolation.md`.
|
|
25
|
+
|
|
26
|
+
## What to Tell Your User
|
|
27
|
+
|
|
28
|
+
Nothing user-visible. This release hardens instar's own development pipeline against a class of bug that, while invisible to running agents, had been quietly corrupting our published README. Agents continue to behave identically.
|
|
29
|
+
|
|
30
|
+
If a contributor asks why their push is suddenly refused with a fixture-pollution guard error, the script's own output includes recovery instructions — usually resetting past a stray commit and pushing again.
|
|
31
|
+
|
|
32
|
+
## Summary of New Capabilities
|
|
33
|
+
|
|
34
|
+
This release is a pure hardening fix. No new runtime capabilities for agents. The new pre-push and CI guards are infrastructure for the instar-developing agent and direct contributors only.
|
|
35
|
+
|
|
36
|
+
## Evidence
|
|
37
|
+
|
|
38
|
+
The README clobber on `main` was reproduced before the fix landed: `gh api repos/JKHeadley/instar/contents/README.md --jq .size` returned `7` (the literal text `# Test\n`) prior to PR #285. PR #285 restored the README; this PR closes the underlying class so the same regression cannot recur.
|
|
39
|
+
|
|
40
|
+
Test-fixture pollution detection was verified end-to-end: against a tmpdir repo with a commit subject `Initial commit`, `node scripts/pre-push-fixture-guard.mjs` exits 1 and names the offending commit. Against the same repo with the env bypass set, the script exits 0. Repo-invariant detection was verified end-to-end: against a repo where `README.md` has been trimmed to a single line, `node scripts/check-repo-invariants.mjs` exits 1 and reports the line count; against a healthy repo, it exits 0.
|
|
41
|
+
|
|
42
|
+
14 new tests across the env-strip regression, the fixture-guard, and the invariants script. The three refactored fixtures (`worktree-monitor.test.ts`, `SafeGitExecutor.test.ts`, `rich-profile-integration.test.ts`) retain their pre-existing 45+ test assertions.
|