@jjh32456/dsh-longlive 1.6.8 → 1.8.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 (288) hide show
  1. package/CHANGELOG.md +69 -1539
  2. package/LICENSE +1 -1
  3. package/QUICKSTART.md +33 -12
  4. package/README.en.md +17 -7
  5. package/README.md +29 -10
  6. package/SPEC.md +5 -5
  7. package/cordis.patch.yml +17 -12
  8. package/docs/ANSWER-GUARD.md +92 -0
  9. package/docs/ARCHITECTURE.md +3 -3
  10. package/docs/AUTO-RECALL-WIRING.md +1 -1
  11. package/docs/AUTO-RECALL.md +15 -9
  12. package/docs/CONFIG.md +143 -37
  13. package/docs/ERRORS.md +17 -5
  14. package/docs/FAQ.md +7 -7
  15. package/docs/FEATURES.md +66 -29
  16. package/docs/MIGRATION-1.6.0.md +2 -2
  17. package/docs/OPERATIONS.md +6 -6
  18. package/docs/PANEL-INTEGRATION.md +6 -0
  19. package/docs/PRIVACY.md +88 -0
  20. package/docs/QUICKSTART.md +5 -5
  21. package/docs/README.en.md +3 -2
  22. package/docs/RESIDENT-WATCHER.md +1 -1
  23. package/docs/SESSION-WATCH.md +6 -6
  24. package/docs/TROUBLESHOOTING.md +4 -4
  25. package/package.json +42 -117
  26. package/runbook.md +2 -2
  27. package/scripts/backfill-auto-cards.mjs +98 -0
  28. package/scripts/install-surgery-task.ps1 +5 -2
  29. package/scripts/lib/session-watch-fixture.mjs +1 -1
  30. package/scripts/longlive-doctor.mjs +6 -0
  31. package/scripts/migrate-180.mjs +163 -0
  32. package/scripts/recall-index-build.mjs +128 -0
  33. package/scripts/session-watch-drill.mjs +1 -1
  34. package/scripts/surgery-run.cmd +1 -1
  35. package/scripts/unattended-surgery.ps1 +5 -2
  36. package/scripts/verify-card-anchors.mjs +138 -0
  37. package/src/actions/audit.js +125 -0
  38. package/src/actions/evidence.js +122 -0
  39. package/src/actions/fs.js +162 -0
  40. package/src/activity/index.js +129 -18
  41. package/src/activity/model.js +11 -1
  42. package/src/activity/store.js +22 -3
  43. package/src/activity/thinking.js +176 -0
  44. package/src/adapters/constants.js +11 -0
  45. package/src/adapters/engram/data/aliases.json +51 -0
  46. package/src/adapters/engram/data/policy.json +8 -0
  47. package/src/adapters/engram/data/routing-rules.json +546 -0
  48. package/src/adapters/engram/index.js +235 -0
  49. package/src/adapters/registry.js +315 -0
  50. package/src/answer/audit.js +193 -0
  51. package/src/answer/evidence.js +30 -0
  52. package/src/answer/guard.js +1370 -0
  53. package/src/answer/index.js +1067 -0
  54. package/src/answer/query.js +111 -0
  55. package/src/answer/second-hop.mjs +101 -0
  56. package/src/auto-truncate.js +1 -1
  57. package/src/config.js +145 -48
  58. package/src/deps.js +6 -4
  59. package/src/features.js +158 -8
  60. package/src/identity/index.js +13 -5
  61. package/src/identity/inject-position.js +2 -2
  62. package/src/index.js +97 -7
  63. package/src/lifecycle/gap-clocks.js +328 -0
  64. package/src/lifecycle/index.js +269 -0
  65. package/src/memory/activity-capture.js +229 -0
  66. package/src/memory/anchors.js +194 -0
  67. package/src/memory/audit-records.js +222 -0
  68. package/src/memory/auto-cards.js +401 -0
  69. package/src/memory/capture.js +442 -0
  70. package/src/memory/card-anchors.js +203 -0
  71. package/src/memory/conflicts.js +106 -0
  72. package/src/memory/decay.js +311 -0
  73. package/src/memory/distill.js +471 -0
  74. package/src/memory/event-index.js +917 -0
  75. package/src/memory/evidence-chain.js +295 -0
  76. package/src/memory/evidence.js +145 -0
  77. package/src/memory/filter-runner.js +1 -1
  78. package/src/memory/filter.js +5 -1
  79. package/src/memory/forget-v2.js +410 -0
  80. package/src/memory/forget.js +71 -0
  81. package/src/memory/frames-activation.js +247 -0
  82. package/src/memory/fts-query.js +174 -0
  83. package/src/memory/governance.js +604 -0
  84. package/src/memory/index.js +1 -0
  85. package/src/memory/l2-gate.js +169 -0
  86. package/src/memory/lock.js +121 -0
  87. package/src/memory/migrate.js +139 -0
  88. package/src/memory/recall-v2.js +223 -0
  89. package/src/memory/recall.js +65 -2
  90. package/src/memory/records.js +425 -0
  91. package/src/memory/retention.js +128 -0
  92. package/src/memory/review.js +124 -0
  93. package/src/memory/trust.js +88 -0
  94. package/src/memory/version-chain.js +264 -0
  95. package/src/memory/wal.js +137 -0
  96. package/src/memory/working-frame.js +516 -0
  97. package/src/metrics/classify.mjs +5 -3
  98. package/src/metrics/index.mjs +21 -1
  99. package/src/metrics/prestep-latency.mjs +300 -0
  100. package/src/metrics/store.mjs +25 -0
  101. package/src/policy-profile.js +18 -3
  102. package/src/recall/anaphora.mjs +87 -0
  103. package/src/recall/cards.mjs +7 -0
  104. package/src/recall/embedding.mjs +8 -8
  105. package/src/recall/entities.mjs +112 -0
  106. package/src/recall/index.js +235 -19
  107. package/src/recall/inject-order.mjs +10 -8
  108. package/src/recall/privacy.mjs +1 -1
  109. package/src/recall/recall-core.mjs +116 -10
  110. package/src/recall/shared-visibility.js +302 -0
  111. package/src/recall/time-normalizer.mjs +196 -0
  112. package/src/role/activation.js +191 -0
  113. package/src/role/attention.js +318 -0
  114. package/src/role/base.js +36 -0
  115. package/src/role/channels.js +423 -0
  116. package/src/role/index.js +425 -0
  117. package/src/role/interfaces.js +115 -0
  118. package/src/role/personality.js +370 -0
  119. package/src/role/readers.js +151 -0
  120. package/src/role/relationships.js +477 -0
  121. package/src/role/store.js +202 -0
  122. package/src/role/writeprotect.js +373 -0
  123. package/src/router/aliases.json +176 -25
  124. package/src/router/background/agent-runner.mjs +1 -1
  125. package/src/router/background/anchor.mjs +1 -1
  126. package/src/router/background/audit.mjs +2 -2
  127. package/src/router/background/budget-ledger.mjs +2 -2
  128. package/src/router/background/host-sim.mjs +1 -1
  129. package/src/router/background/memory.mjs +4 -4
  130. package/src/router/background/queue.mjs +1 -1
  131. package/src/router/background/retry.mjs +1 -1
  132. package/src/router/background/router.mjs +1 -1
  133. package/src/router/background/stream.mjs +1 -1
  134. package/src/router/background/tool-policy.mjs +1 -1
  135. package/src/router/design-headings.json +5 -5
  136. package/src/router/face/config.mjs +1 -1
  137. package/src/router/face/identity.mjs +3 -3
  138. package/src/router/judge.mjs +8 -7
  139. package/src/router/policies/generic.json +21 -4
  140. package/src/router/route.js +109 -22
  141. package/src/router/router.mjs +10 -6
  142. package/src/router/routing-rules.json +260 -445
  143. package/src/router/run-sim.mjs +8 -8
  144. package/src/router/runtime.js +23 -12
  145. package/src/router/service/config.mjs +5 -5
  146. package/src/router/service/context.mjs +1 -1
  147. package/src/router/service/queue.mjs +1 -1
  148. package/src/router/shadow.mjs +4 -3
  149. package/src/surgery-recovery.js +2 -2
  150. package/src/tool-output.js +2 -2
  151. package/src/tools.js +39 -7
  152. package/src/truncate-visible.js +2 -2
  153. package/src/ui/actions.js +1312 -0
  154. package/src/ui/panel-api.js +367 -0
  155. package/src/ui/panel-auth.js +251 -0
  156. package/src/ui/panel-integrity.js +243 -0
  157. package/src/ui/routes-readonly.js +695 -0
  158. package/src/ui/routes.js +3 -0
  159. package/docs/DEV.md +0 -279
  160. package/docs/PRESENCE-BUS-WIRING.md +0 -491
  161. package/docs/PRESENCE-BUS.md +0 -694
  162. package/docs/SURGERY.md +0 -186
  163. package/scripts/mark-core-cards.mjs +0 -289
  164. package/scripts/pack.mjs +0 -392
  165. package/scripts/router-proposals.mjs +0 -386
  166. package/src/router/fixtures/M1.json +0 -16
  167. package/src/router/fixtures/M2.json +0 -16
  168. package/src/router/fixtures/M3.json +0 -20
  169. package/src/router/fixtures/M4.json +0 -20
  170. package/src/router/fixtures/M5.json +0 -45
  171. package/src/router/fixtures/M6.json +0 -53
  172. package/src/router/fixtures/M7.json +0 -46
  173. package/src/router/fixtures/S1.json +0 -20
  174. package/src/router/fixtures/S2.json +0 -23
  175. package/src/router/fixtures/S3.json +0 -20
  176. package/src/router/fixtures/S4.json +0 -34
  177. package/src/router/fixtures/S5.json +0 -43
  178. package/src/router/fixtures/S6.json +0 -47
  179. package/src/router/fixtures/T1.json +0 -23
  180. package/src/router/fixtures/T2.json +0 -16
  181. package/src/router/fixtures/T3.json +0 -19
  182. package/src/router/fixtures/T4.json +0 -16
  183. package/src/router/fixtures/X1.json +0 -17
  184. package/src/router/fixtures/X2.json +0 -20
  185. package/src/router/gen-design-headings.mjs +0 -107
  186. package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
  187. package/test/activity-realtime.test.mjs +0 -959
  188. package/test/activity-stream.test.mjs +0 -512
  189. package/test/auto-recall.test.mjs +0 -474
  190. package/test/auto-truncate.test.mjs +0 -931
  191. package/test/autobind.test.mjs +0 -399
  192. package/test/bus.test.mjs +0 -1572
  193. package/test/cli-guard.test.mjs +0 -198
  194. package/test/conclusion.test.mjs +0 -408
  195. package/test/config-schema.test.mjs +0 -210
  196. package/test/d6-output.test.mjs +0 -194
  197. package/test/d6.test.mjs +0 -395
  198. package/test/deps.test.mjs +0 -342
  199. package/test/doctor-setup.test.mjs +0 -297
  200. package/test/embedding-recall.test.mjs +0 -347
  201. package/test/feature-flags.test.mjs +0 -585
  202. package/test/filter-shadow.test.mjs +0 -170
  203. package/test/fixtures/activity-append-child.mjs +0 -29
  204. package/test/fixtures/baseline-0.8.0-registration.json +0 -556
  205. package/test/fixtures/d6-header-tools.json +0 -1
  206. package/test/fixtures/lock-child.mjs +0 -67
  207. package/test/fixtures/pre-step-container.mjs +0 -100
  208. package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
  209. package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
  210. package/test/fixtures/profile-session-longlive-config.json +0 -20
  211. package/test/fixtures/proposal-cas-child.mjs +0 -66
  212. package/test/host-engine-status.test.mjs +0 -307
  213. package/test/hot-truncate.test.mjs +0 -1349
  214. package/test/identity-card-marker.test.mjs +0 -307
  215. package/test/identity-router/run.mjs +0 -721
  216. package/test/integration-pre-step.test.mjs +0 -553
  217. package/test/live.test.mjs +0 -1023
  218. package/test/maintenance-schedule.test.mjs +0 -538
  219. package/test/memory.test.mjs +0 -2828
  220. package/test/metrics.test.mjs +0 -478
  221. package/test/orchestrator.test.mjs +0 -266
  222. package/test/p1_probe.mjs +0 -97
  223. package/test/packaging.test.mjs +0 -184
  224. package/test/portability.test.mjs +0 -343
  225. package/test/rc4-regressions.test.mjs +0 -562
  226. package/test/rc5-regressions.test.mjs +0 -197
  227. package/test/rc6-chinese.test.mjs +0 -394
  228. package/test/router-1100/apply-off.test.mjs +0 -175
  229. package/test/router-1100/budget-reconcile.test.mjs +0 -215
  230. package/test/router-1100/config-context.test.mjs +0 -211
  231. package/test/router-1100/helpers.mjs +0 -129
  232. package/test/router-1100/hint-envelope.test.mjs +0 -150
  233. package/test/router-1100/lock-crossproc.test.mjs +0 -206
  234. package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
  235. package/test/router-1100/router-apply.test.mjs +0 -372
  236. package/test/router-1100/router-fixb.test.mjs +0 -602
  237. package/test/router-1100/router-proposals.test.mjs +0 -405
  238. package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
  239. package/test/router-1100/router-security-cr.test.mjs +0 -333
  240. package/test/router-1100/service-lifecycle.test.mjs +0 -220
  241. package/test/router-1100/tools-contract.test.mjs +0 -164
  242. package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
  243. package/test/router-1100/v6-contract.test.mjs +0 -158
  244. package/test/router-1100/v6b-contract.test.mjs +0 -293
  245. package/test/router-1100/v6c-contract.test.mjs +0 -249
  246. package/test/router-1100/wal-rotation.test.mjs +0 -375
  247. package/test/router-1100/wp1-config.test.mjs +0 -170
  248. package/test/router-1100/wp1-default-off.test.mjs +0 -77
  249. package/test/router-1100/wp1-model-status.test.mjs +0 -90
  250. package/test/router-1100/wp1-status.test.mjs +0 -220
  251. package/test/router-1100/wp1-tools.test.mjs +0 -146
  252. package/test/router-1100/wp5-cli.test.mjs +0 -240
  253. package/test/router-1100/wp5-model-auto.test.mjs +0 -418
  254. package/test/router-1100/wp6-merge.test.mjs +0 -386
  255. package/test/router-1100/wp6-wake.test.mjs +0 -502
  256. package/test/router-background/audit.test.mjs +0 -37
  257. package/test/router-background/budget-ledger.test.mjs +0 -330
  258. package/test/router-background/cross-process.test.mjs +0 -222
  259. package/test/router-background/fixtures/cross-child.mjs +0 -85
  260. package/test/router-background/fixtures/real-shapes.mjs +0 -239
  261. package/test/router-background/router.test.mjs +0 -1076
  262. package/test/router-background/stream-retry.test.mjs +0 -358
  263. package/test/router-background/tool-agent.test.mjs +0 -384
  264. package/test/router-face/bridge.test.mjs +0 -169
  265. package/test/router-face/face.test.mjs +0 -316
  266. package/test/router-failclosed.test.mjs +0 -466
  267. package/test/router-identity.test.mjs +0 -384
  268. package/test/router-l1-l2.contract.test.mjs +0 -1075
  269. package/test/router-memory-tools.test.mjs +0 -262
  270. package/test/router-policy.test.mjs +0 -225
  271. package/test/router-runtime.test.mjs +0 -438
  272. package/test/router-shadow.test.mjs +0 -287
  273. package/test/router.test.mjs +0 -903
  274. package/test/run-rc.mjs +0 -315
  275. package/test/run.mjs +0 -2469
  276. package/test/safety-defaults.test.mjs +0 -181
  277. package/test/seeded-load.test.mjs +0 -436
  278. package/test/session-original.test.mjs +0 -338
  279. package/test/state-truth.test.mjs +0 -937
  280. package/test/surgery-recovery.test.mjs +0 -936
  281. package/test/t7_drive.mjs +0 -151
  282. package/test/tool-output.test.mjs +0 -393
  283. package/test/tool-schema.test.mjs +0 -177
  284. package/test/tools_contract.test.mjs +0 -716
  285. package/test/truncate-visible.test.mjs +0 -1150
  286. package/test/ui-flags.test.mjs +0 -477
  287. package/test/v4-hardening.test.mjs +0 -427
  288. package/test/version-consistency.test.mjs +0 -286
package/scripts/pack.mjs DELETED
@@ -1,392 +0,0 @@
1
- #!/usr/bin/env node
2
- // Build a self-identifying package without npm.
3
- //
4
- // The source tree's package.json version is a claim; this script binds that
5
- // claim to the bytes it ships:
6
- // * the version is read from package.json through src/version.js and is
7
- // never restated here: a malformed version fails the build, and an
8
- // optional --expect-version pin must match package.json exactly;
9
- // * it applies the `files` whitelist, including explicit `!` exclusions for
10
- // npm's README*/LICENSE* forced-include rule;
11
- // * it writes `package/build-info.json` with a sha256 for every shipped file
12
- // and one digest over the whole payload;
13
- // * it writes `<archive>.sha256` next to the tarball.
14
- //
15
- // Only Node built-ins are used: gzip + a minimal ustar writer/reader.
16
-
17
- import fs from 'node:fs';
18
- import path from 'node:path';
19
- import crypto from 'node:crypto';
20
- import zlib from 'node:zlib';
21
- import { fileURLToPath } from 'node:url';
22
-
23
- import { readPackageManifest, validateVersion } from '../src/version.js';
24
-
25
- const HERE = path.dirname(fileURLToPath(import.meta.url));
26
- const ROOT = path.resolve(HERE, '..');
27
- const P1_PROBE = 'test/p1_probe.mjs';
28
- const BUILD_INFO = 'build-info.json';
29
- const TAR_BLOCK = 512;
30
- const SKIP_DIRS = new Set(['node_modules', '.git', '.longlive', 'dist', '.cache']);
31
-
32
- function fail(message) {
33
- process.stderr.write(`pack: ${message}\n`);
34
- process.exit(1);
35
- }
36
-
37
- function sha256(buffer) {
38
- return crypto.createHash('sha256').update(buffer).digest('hex');
39
- }
40
-
41
- function toPosix(value) {
42
- return value.split(path.sep).join('/');
43
- }
44
-
45
- /** Recursively list regular files under ROOT, skipping build/VCS directories. */
46
- function walk(dir = ROOT, out = []) {
47
- for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
48
- if (dirent.isSymbolicLink()) continue;
49
- const abs = path.join(dir, dirent.name);
50
- if (dirent.isDirectory()) {
51
- if (SKIP_DIRS.has(dirent.name)) continue;
52
- walk(abs, out);
53
- continue;
54
- }
55
- if (!dirent.isFile()) continue;
56
- out.push(toPosix(path.relative(ROOT, abs)));
57
- }
58
- return out;
59
- }
60
-
61
- /** Minimal glob: `**` crosses directories, `*` and `?` do not. */
62
- function globToRegExp(pattern) {
63
- let source = '';
64
- for (let i = 0; i < pattern.length; i += 1) {
65
- const char = pattern[i];
66
- if (char === '*') {
67
- if (pattern[i + 1] === '*') {
68
- i += 1;
69
- if (pattern[i + 1] === '/') {
70
- i += 1;
71
- source += '(?:.*/)?';
72
- } else {
73
- source += '.*';
74
- }
75
- } else {
76
- source += '[^/]*';
77
- }
78
- } else if (char === '?') {
79
- source += '[^/]';
80
- } else {
81
- source += char.replace(/[.+^${}()|[\]\\]/g, '\\$&');
82
- }
83
- }
84
- return new RegExp(`^${source}$`);
85
- }
86
-
87
- function selectFiles(pkg) {
88
- const patterns = Array.isArray(pkg.files) ? pkg.files : [];
89
- if (patterns.length === 0) fail('package.json has no files whitelist');
90
- const positives = patterns.filter((p) => !p.startsWith('!'));
91
- const negatives = patterns.filter((p) => p.startsWith('!')).map((p) => p.slice(1));
92
- if (negatives.length === 0) fail('package.json files must contain explicit "!" exclusions');
93
-
94
- const positiveMatchers = positives.map((pattern) => ({ pattern, re: globToRegExp(pattern) }));
95
- const negativeMatchers = negatives.map((pattern) => ({ pattern, re: globToRegExp(pattern) }));
96
- const all = walk();
97
- const selected = new Set();
98
-
99
- for (const rel of all) {
100
- if (positiveMatchers.some(({ re }) => re.test(rel))) selected.add(rel);
101
- }
102
-
103
- // npm always includes package.json plus root README*/LICENSE*/CHANGELOG*
104
- // even when `files` is a whitelist. Mirror that, then let `!` remove the
105
- // backups npm would otherwise drag in (README.md.bak-* etc.).
106
- selected.add('package.json');
107
- for (const rel of all) {
108
- if (rel.includes('/')) continue;
109
- if (/^(README|LICENSE|LICENCE|CHANGELOG|HISTORY)(\..*)?$/i.test(rel)) selected.add(rel);
110
- }
111
-
112
- for (const rel of [...selected]) {
113
- if (negativeMatchers.some(({ re }) => re.test(rel))) selected.delete(rel);
114
- }
115
-
116
- const files = [...selected].sort();
117
- const strays = files.filter((rel) => rel.includes('.bak-'));
118
- if (strays.length > 0) {
119
- fail(`backup file leaked into the package (add a "!" exclusion): ${strays.join(', ')}`);
120
- }
121
- if (!files.includes(P1_PROBE)) fail(`${P1_PROBE} is missing from the package`);
122
- if (!files.includes('package.json')) fail('package.json is missing from the package');
123
- return {
124
- files,
125
- excludedBackups: all.filter((rel) => rel.includes('.bak-')).sort(),
126
- };
127
- }
128
-
129
- function octal(value, width) {
130
- const text = Math.max(0, value).toString(8);
131
- if (text.length > width - 1) throw new Error(`octal overflow: ${value}`);
132
- return `${'0'.repeat(width - 1 - text.length)}${text}\0`;
133
- }
134
-
135
- function splitTarName(rel) {
136
- const full = `package/${rel}`;
137
- if (Buffer.byteLength(full) <= 100) return { name: full, prefix: '' };
138
- const parts = full.split('/');
139
- for (let i = parts.length - 1; i > 0; i -= 1) {
140
- const prefix = parts.slice(0, i).join('/');
141
- const name = parts.slice(i).join('/');
142
- if (Buffer.byteLength(prefix) <= 155 && Buffer.byteLength(name) <= 100) {
143
- return { name, prefix };
144
- }
145
- }
146
- throw new Error(`path too long for ustar: ${rel}`);
147
- }
148
-
149
- function tarHeader(rel, size) {
150
- const { name, prefix } = splitTarName(rel);
151
- const header = Buffer.alloc(TAR_BLOCK, 0);
152
- header.write(name, 0, 100, 'utf8');
153
- header.write(octal(0o644, 8), 100, 8, 'ascii');
154
- header.write(octal(0, 8), 108, 8, 'ascii');
155
- header.write(octal(0, 8), 116, 8, 'ascii');
156
- header.write(octal(size, 12), 124, 12, 'ascii');
157
- header.write(octal(0, 12), 136, 12, 'ascii'); // mtime 0 -> reproducible
158
- header.write(' ', 148, 8, 'ascii'); // checksum placeholder
159
- header.write('0', 156, 1, 'ascii');
160
- header.write('ustar\0', 257, 6, 'ascii');
161
- header.write('00', 263, 2, 'ascii');
162
- header.write(octal(0, 8), 329, 8, 'ascii');
163
- header.write(octal(0, 8), 337, 8, 'ascii');
164
- header.write(prefix, 345, 155, 'utf8');
165
- let checksum = 0;
166
- for (const byte of header) checksum += byte;
167
- header.write(`${checksum.toString(8).padStart(6, '0')}\0 `, 148, 8, 'ascii');
168
- return header;
169
- }
170
-
171
- function makeTar(entries) {
172
- const chunks = [];
173
- for (const entry of entries) {
174
- chunks.push(tarHeader(entry.path, entry.bytes.length));
175
- chunks.push(entry.bytes);
176
- const pad = (TAR_BLOCK - (entry.bytes.length % TAR_BLOCK)) % TAR_BLOCK;
177
- if (pad > 0) chunks.push(Buffer.alloc(pad, 0));
178
- }
179
- chunks.push(Buffer.alloc(TAR_BLOCK * 2, 0));
180
- return Buffer.concat(chunks);
181
- }
182
-
183
- function parseTar(buffer) {
184
- const entries = [];
185
- for (let offset = 0; offset + TAR_BLOCK <= buffer.length;) {
186
- const header = buffer.subarray(offset, offset + TAR_BLOCK);
187
- if (header.every((byte) => byte === 0)) break;
188
- const name = header.subarray(0, 100).toString('utf8').replace(/\0.*$/, '');
189
- const prefix = header.subarray(345, 500).toString('utf8').replace(/\0.*$/, '');
190
- const size = parseInt(header.subarray(124, 136).toString('ascii').replace(/\0.*$/, '').trim(), 8);
191
- const type = header.subarray(156, 157).toString('ascii');
192
- if (!Number.isFinite(size) || size < 0) throw new Error(`bad tar size at ${offset}`);
193
- const start = offset + TAR_BLOCK;
194
- const body = buffer.subarray(start, start + size);
195
- if (body.length !== size) throw new Error(`truncated tar entry ${name}`);
196
- const full = prefix ? `${prefix}/${name}` : name;
197
- if (type === '0') entries.push({ path: full, bytes: body });
198
- offset = start + size + ((TAR_BLOCK - (size % TAR_BLOCK)) % TAR_BLOCK);
199
- }
200
- return entries;
201
- }
202
-
203
- function payloadManifest(files) {
204
- const rows = files.map((rel) => {
205
- const bytes = fs.readFileSync(path.join(ROOT, rel));
206
- return { path: rel, bytes: bytes.length, sha256: sha256(bytes) };
207
- });
208
- const canonical = rows.map((row) => `${row.sha256} ${row.path}\n`).join('');
209
- return {
210
- files: rows,
211
- sourceTreeSha256: sha256(canonical),
212
- };
213
- }
214
-
215
- function build(options) {
216
- const pkg = readPackageManifest(ROOT);
217
- let version;
218
- try {
219
- version = validateVersion(pkg.version, path.join(ROOT, 'package.json'));
220
- } catch (error) {
221
- fail(error.message);
222
- }
223
- // An optional --expect-version pin (release scripts pass it) must agree with
224
- // package.json; without it the package.json version is the only authority.
225
- const expectedVersion = options.expectVersion ?? version;
226
- if (expectedVersion !== version) {
227
- fail(`package.json version is ${JSON.stringify(version)}, ` +
228
- `which does not match --expect-version ${JSON.stringify(expectedVersion)}`);
229
- }
230
- if (pkg.name !== '@jjh32456/dsh-longlive') {
231
- fail(`package name is ${JSON.stringify(pkg.name)}`);
232
- }
233
- const { files, excludedBackups } = selectFiles(pkg);
234
- const manifest = payloadManifest(files);
235
- const payload = files.map((rel) => ({
236
- path: rel,
237
- bytes: fs.readFileSync(path.join(ROOT, rel)),
238
- }));
239
-
240
- const buildInfo = {
241
- schema: 1,
242
- name: pkg.name,
243
- version,
244
- expectedVersion,
245
- builtBy: 'scripts/pack.mjs',
246
- generatedAt: process.env.SOURCE_DATE_EPOCH
247
- ? new Date(Number(process.env.SOURCE_DATE_EPOCH) * 1000).toISOString()
248
- : new Date().toISOString(),
249
- fileCount: manifest.files.length,
250
- sourceTreeSha256: manifest.sourceTreeSha256,
251
- manifestExcludes: [BUILD_INFO],
252
- files: manifest.files,
253
- };
254
- const buildInfoBytes = Buffer.from(`${JSON.stringify(buildInfo, null, 2)}\n`, 'utf8');
255
-
256
- const entries = [
257
- ...payload.map((entry) => ({ path: entry.path, bytes: entry.bytes })),
258
- { path: BUILD_INFO, bytes: buildInfoBytes },
259
- ];
260
- const tar = makeTar(entries);
261
- const gz = zlib.gzipSync(tar, { level: 9 });
262
- const out = path.resolve(options.out ?? path.join(ROOT, 'dist', `jjh32456-dsh-longlive-${version}.tgz`));
263
- fs.mkdirSync(path.dirname(out), { recursive: true });
264
- fs.writeFileSync(out, gz);
265
- const digest = sha256(gz);
266
- const sidecar = `${out}.sha256`;
267
- fs.writeFileSync(sidecar, `${digest} ${path.basename(out)}\n`, 'utf8');
268
-
269
- return {
270
- ok: true,
271
- name: pkg.name,
272
- version,
273
- archive: out,
274
- sha256: digest,
275
- bytes: gz.length,
276
- fileCount: files.length,
277
- sourceTreeSha256: manifest.sourceTreeSha256,
278
- p1Probe: files.includes(P1_PROBE),
279
- backupFiles: [],
280
- excludedBackups,
281
- sidecar,
282
- };
283
- }
284
-
285
- function verify(archivePath, options = {}) {
286
- const archive = path.resolve(archivePath);
287
- const gz = fs.readFileSync(archive);
288
- const digest = sha256(gz);
289
- const entries = parseTar(zlib.gunzipSync(gz));
290
- const byPath = new Map(entries.map((entry) => [entry.path, entry]));
291
- const infoEntry = byPath.get(`package/${BUILD_INFO}`);
292
- if (!infoEntry) throw new Error(`package/${BUILD_INFO} is missing from the archive`);
293
- const info = JSON.parse(infoEntry.bytes.toString('utf8'));
294
-
295
- if (info.version !== info.expectedVersion) {
296
- throw new Error(
297
- `build-info expectedVersion ${JSON.stringify(info.expectedVersion)} != version ${JSON.stringify(info.version)}`);
298
- }
299
- const version = validateVersion(info.version, `package/${BUILD_INFO}`);
300
- if (options.expectVersion && options.expectVersion !== version) {
301
- throw new Error(
302
- `archive version ${JSON.stringify(version)} does not match --expect-version ${JSON.stringify(options.expectVersion)}`);
303
- }
304
- if (info.name !== '@jjh32456/dsh-longlive') throw new Error(`build-info name ${JSON.stringify(info.name)}`);
305
- if (!Array.isArray(info.files) || info.files.length === 0) throw new Error('build-info has no file manifest');
306
-
307
- const paths = new Set();
308
- for (const row of info.files) {
309
- if (typeof row.path !== 'string' || row.path.includes('..') || path.isAbsolute(row.path)) {
310
- throw new Error(`unsafe manifest path: ${JSON.stringify(row.path)}`);
311
- }
312
- if (paths.has(row.path)) throw new Error(`duplicate manifest path: ${row.path}`);
313
- paths.add(row.path);
314
- const entry = byPath.get(`package/${row.path}`);
315
- if (!entry) throw new Error(`manifest file missing from archive: ${row.path}`);
316
- const actual = sha256(entry.bytes);
317
- if (actual !== row.sha256) throw new Error(`sha256 mismatch for ${row.path}: ${actual} != ${row.sha256}`);
318
- if (entry.bytes.length !== row.bytes) throw new Error(`size mismatch for ${row.path}`);
319
- }
320
- if (!paths.has(P1_PROBE)) throw new Error(`${P1_PROBE} missing from build-info manifest`);
321
-
322
- const rebuilt = info.files.map((row) => `${row.sha256} ${row.path}\n`).join('');
323
- const sourceTreeSha256 = sha256(rebuilt);
324
- if (sourceTreeSha256 !== info.sourceTreeSha256) {
325
- throw new Error(`sourceTreeSha256 mismatch: ${sourceTreeSha256} != ${info.sourceTreeSha256}`);
326
- }
327
-
328
- const backupFiles = entries.map((entry) => entry.path).filter((name) => name.includes('.bak-'));
329
- if (backupFiles.length > 0) throw new Error(`backup files in archive: ${backupFiles.join(', ')}`);
330
-
331
- const extra = entries
332
- .map((entry) => entry.path)
333
- .filter((name) => name.startsWith('package/') && name !== `package/${BUILD_INFO}`)
334
- .filter((name) => !paths.has(name.slice('package/'.length)));
335
- if (extra.length > 0) throw new Error(`archive files absent from manifest: ${extra.join(', ')}`);
336
-
337
- const sidecarPath = `${archive}.sha256`;
338
- let sidecarOk = null;
339
- if (fs.existsSync(sidecarPath)) {
340
- const declared = fs.readFileSync(sidecarPath, 'utf8').trim().split(/\s+/)[0];
341
- sidecarOk = declared === digest;
342
- if (!sidecarOk) throw new Error(`sidecar sha256 mismatch: ${declared} != ${digest}`);
343
- }
344
-
345
- return {
346
- ok: true,
347
- version,
348
- archive,
349
- sha256: digest,
350
- bytes: gz.length,
351
- fileCount: info.files.length,
352
- sourceTreeSha256,
353
- p1Probe: true,
354
- backupFiles,
355
- sidecarOk,
356
- };
357
- }
358
-
359
- function takeValue(args, flag) {
360
- const index = args.indexOf(flag);
361
- if (index === -1) return undefined;
362
- const value = args[index + 1];
363
- if (!value || value.startsWith('--')) fail(`${flag} needs a value`);
364
- args.splice(index, 2);
365
- return value;
366
- }
367
-
368
- const args = process.argv.slice(2);
369
- const json = args.includes('--json');
370
- const verifyPath = takeValue(args, '--verify');
371
- const out = takeValue(args, '--out');
372
- const expectVersion = takeValue(args, '--expect-version');
373
- if (expectVersion !== undefined) {
374
- try {
375
- validateVersion(expectVersion, '--expect-version');
376
- } catch (error) {
377
- fail(error.message);
378
- }
379
- }
380
- for (const arg of args) {
381
- if (arg !== '--json') fail(`unknown argument: ${arg}`);
382
- }
383
-
384
- try {
385
- const result = verifyPath
386
- ? verify(verifyPath, { expectVersion })
387
- : build({ out, expectVersion });
388
- process.stdout.write(`${JSON.stringify(result, null, json ? 0 : 2)}\n`);
389
- } catch (error) {
390
- fail(error?.message ?? String(error));
391
- }
392
-