@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.
- package/CHANGELOG.md +69 -1539
- package/LICENSE +1 -1
- package/QUICKSTART.md +33 -12
- package/README.en.md +17 -7
- package/README.md +29 -10
- package/SPEC.md +5 -5
- package/cordis.patch.yml +17 -12
- package/docs/ANSWER-GUARD.md +92 -0
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-RECALL-WIRING.md +1 -1
- package/docs/AUTO-RECALL.md +15 -9
- package/docs/CONFIG.md +143 -37
- package/docs/ERRORS.md +17 -5
- package/docs/FAQ.md +7 -7
- package/docs/FEATURES.md +66 -29
- package/docs/MIGRATION-1.6.0.md +2 -2
- package/docs/OPERATIONS.md +6 -6
- package/docs/PANEL-INTEGRATION.md +6 -0
- package/docs/PRIVACY.md +88 -0
- package/docs/QUICKSTART.md +5 -5
- package/docs/README.en.md +3 -2
- package/docs/RESIDENT-WATCHER.md +1 -1
- package/docs/SESSION-WATCH.md +6 -6
- package/docs/TROUBLESHOOTING.md +4 -4
- package/package.json +42 -117
- package/runbook.md +2 -2
- package/scripts/backfill-auto-cards.mjs +98 -0
- package/scripts/install-surgery-task.ps1 +5 -2
- package/scripts/lib/session-watch-fixture.mjs +1 -1
- package/scripts/longlive-doctor.mjs +6 -0
- package/scripts/migrate-180.mjs +163 -0
- package/scripts/recall-index-build.mjs +128 -0
- package/scripts/session-watch-drill.mjs +1 -1
- package/scripts/surgery-run.cmd +1 -1
- package/scripts/unattended-surgery.ps1 +5 -2
- package/scripts/verify-card-anchors.mjs +138 -0
- package/src/actions/audit.js +125 -0
- package/src/actions/evidence.js +122 -0
- package/src/actions/fs.js +162 -0
- package/src/activity/index.js +129 -18
- package/src/activity/model.js +11 -1
- package/src/activity/store.js +22 -3
- package/src/activity/thinking.js +176 -0
- package/src/adapters/constants.js +11 -0
- package/src/adapters/engram/data/aliases.json +51 -0
- package/src/adapters/engram/data/policy.json +8 -0
- package/src/adapters/engram/data/routing-rules.json +546 -0
- package/src/adapters/engram/index.js +235 -0
- package/src/adapters/registry.js +315 -0
- package/src/answer/audit.js +193 -0
- package/src/answer/evidence.js +30 -0
- package/src/answer/guard.js +1370 -0
- package/src/answer/index.js +1067 -0
- package/src/answer/query.js +111 -0
- package/src/answer/second-hop.mjs +101 -0
- package/src/auto-truncate.js +1 -1
- package/src/config.js +145 -48
- package/src/deps.js +6 -4
- package/src/features.js +158 -8
- package/src/identity/index.js +13 -5
- package/src/identity/inject-position.js +2 -2
- package/src/index.js +97 -7
- package/src/lifecycle/gap-clocks.js +328 -0
- package/src/lifecycle/index.js +269 -0
- package/src/memory/activity-capture.js +229 -0
- package/src/memory/anchors.js +194 -0
- package/src/memory/audit-records.js +222 -0
- package/src/memory/auto-cards.js +401 -0
- package/src/memory/capture.js +442 -0
- package/src/memory/card-anchors.js +203 -0
- package/src/memory/conflicts.js +106 -0
- package/src/memory/decay.js +311 -0
- package/src/memory/distill.js +471 -0
- package/src/memory/event-index.js +917 -0
- package/src/memory/evidence-chain.js +295 -0
- package/src/memory/evidence.js +145 -0
- package/src/memory/filter-runner.js +1 -1
- package/src/memory/filter.js +5 -1
- package/src/memory/forget-v2.js +410 -0
- package/src/memory/forget.js +71 -0
- package/src/memory/frames-activation.js +247 -0
- package/src/memory/fts-query.js +174 -0
- package/src/memory/governance.js +604 -0
- package/src/memory/index.js +1 -0
- package/src/memory/l2-gate.js +169 -0
- package/src/memory/lock.js +121 -0
- package/src/memory/migrate.js +139 -0
- package/src/memory/recall-v2.js +223 -0
- package/src/memory/recall.js +65 -2
- package/src/memory/records.js +425 -0
- package/src/memory/retention.js +128 -0
- package/src/memory/review.js +124 -0
- package/src/memory/trust.js +88 -0
- package/src/memory/version-chain.js +264 -0
- package/src/memory/wal.js +137 -0
- package/src/memory/working-frame.js +516 -0
- package/src/metrics/classify.mjs +5 -3
- package/src/metrics/index.mjs +21 -1
- package/src/metrics/prestep-latency.mjs +300 -0
- package/src/metrics/store.mjs +25 -0
- package/src/policy-profile.js +18 -3
- package/src/recall/anaphora.mjs +87 -0
- package/src/recall/cards.mjs +7 -0
- package/src/recall/embedding.mjs +8 -8
- package/src/recall/entities.mjs +112 -0
- package/src/recall/index.js +235 -19
- package/src/recall/inject-order.mjs +10 -8
- package/src/recall/privacy.mjs +1 -1
- package/src/recall/recall-core.mjs +116 -10
- package/src/recall/shared-visibility.js +302 -0
- package/src/recall/time-normalizer.mjs +196 -0
- package/src/role/activation.js +191 -0
- package/src/role/attention.js +318 -0
- package/src/role/base.js +36 -0
- package/src/role/channels.js +423 -0
- package/src/role/index.js +425 -0
- package/src/role/interfaces.js +115 -0
- package/src/role/personality.js +370 -0
- package/src/role/readers.js +151 -0
- package/src/role/relationships.js +477 -0
- package/src/role/store.js +202 -0
- package/src/role/writeprotect.js +373 -0
- package/src/router/aliases.json +176 -25
- package/src/router/background/agent-runner.mjs +1 -1
- package/src/router/background/anchor.mjs +1 -1
- package/src/router/background/audit.mjs +2 -2
- package/src/router/background/budget-ledger.mjs +2 -2
- package/src/router/background/host-sim.mjs +1 -1
- package/src/router/background/memory.mjs +4 -4
- package/src/router/background/queue.mjs +1 -1
- package/src/router/background/retry.mjs +1 -1
- package/src/router/background/router.mjs +1 -1
- package/src/router/background/stream.mjs +1 -1
- package/src/router/background/tool-policy.mjs +1 -1
- package/src/router/design-headings.json +5 -5
- package/src/router/face/config.mjs +1 -1
- package/src/router/face/identity.mjs +3 -3
- package/src/router/judge.mjs +8 -7
- package/src/router/policies/generic.json +21 -4
- package/src/router/route.js +109 -22
- package/src/router/router.mjs +10 -6
- package/src/router/routing-rules.json +260 -445
- package/src/router/run-sim.mjs +8 -8
- package/src/router/runtime.js +23 -12
- package/src/router/service/config.mjs +5 -5
- package/src/router/service/context.mjs +1 -1
- package/src/router/service/queue.mjs +1 -1
- package/src/router/shadow.mjs +4 -3
- package/src/surgery-recovery.js +2 -2
- package/src/tool-output.js +2 -2
- package/src/tools.js +39 -7
- package/src/truncate-visible.js +2 -2
- package/src/ui/actions.js +1312 -0
- package/src/ui/panel-api.js +367 -0
- package/src/ui/panel-auth.js +251 -0
- package/src/ui/panel-integrity.js +243 -0
- package/src/ui/routes-readonly.js +695 -0
- package/src/ui/routes.js +3 -0
- package/docs/DEV.md +0 -279
- package/docs/PRESENCE-BUS-WIRING.md +0 -491
- package/docs/PRESENCE-BUS.md +0 -694
- package/docs/SURGERY.md +0 -186
- package/scripts/mark-core-cards.mjs +0 -289
- package/scripts/pack.mjs +0 -392
- package/scripts/router-proposals.mjs +0 -386
- package/src/router/fixtures/M1.json +0 -16
- package/src/router/fixtures/M2.json +0 -16
- package/src/router/fixtures/M3.json +0 -20
- package/src/router/fixtures/M4.json +0 -20
- package/src/router/fixtures/M5.json +0 -45
- package/src/router/fixtures/M6.json +0 -53
- package/src/router/fixtures/M7.json +0 -46
- package/src/router/fixtures/S1.json +0 -20
- package/src/router/fixtures/S2.json +0 -23
- package/src/router/fixtures/S3.json +0 -20
- package/src/router/fixtures/S4.json +0 -34
- package/src/router/fixtures/S5.json +0 -43
- package/src/router/fixtures/S6.json +0 -47
- package/src/router/fixtures/T1.json +0 -23
- package/src/router/fixtures/T2.json +0 -16
- package/src/router/fixtures/T3.json +0 -19
- package/src/router/fixtures/T4.json +0 -16
- package/src/router/fixtures/X1.json +0 -17
- package/src/router/fixtures/X2.json +0 -20
- package/src/router/gen-design-headings.mjs +0 -107
- package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
- package/test/activity-realtime.test.mjs +0 -959
- package/test/activity-stream.test.mjs +0 -512
- package/test/auto-recall.test.mjs +0 -474
- package/test/auto-truncate.test.mjs +0 -931
- package/test/autobind.test.mjs +0 -399
- package/test/bus.test.mjs +0 -1572
- package/test/cli-guard.test.mjs +0 -198
- package/test/conclusion.test.mjs +0 -408
- package/test/config-schema.test.mjs +0 -210
- package/test/d6-output.test.mjs +0 -194
- package/test/d6.test.mjs +0 -395
- package/test/deps.test.mjs +0 -342
- package/test/doctor-setup.test.mjs +0 -297
- package/test/embedding-recall.test.mjs +0 -347
- package/test/feature-flags.test.mjs +0 -585
- package/test/filter-shadow.test.mjs +0 -170
- package/test/fixtures/activity-append-child.mjs +0 -29
- package/test/fixtures/baseline-0.8.0-registration.json +0 -556
- package/test/fixtures/d6-header-tools.json +0 -1
- package/test/fixtures/lock-child.mjs +0 -67
- package/test/fixtures/pre-step-container.mjs +0 -100
- package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
- package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
- package/test/fixtures/profile-session-longlive-config.json +0 -20
- package/test/fixtures/proposal-cas-child.mjs +0 -66
- package/test/host-engine-status.test.mjs +0 -307
- package/test/hot-truncate.test.mjs +0 -1349
- package/test/identity-card-marker.test.mjs +0 -307
- package/test/identity-router/run.mjs +0 -721
- package/test/integration-pre-step.test.mjs +0 -553
- package/test/live.test.mjs +0 -1023
- package/test/maintenance-schedule.test.mjs +0 -538
- package/test/memory.test.mjs +0 -2828
- package/test/metrics.test.mjs +0 -478
- package/test/orchestrator.test.mjs +0 -266
- package/test/p1_probe.mjs +0 -97
- package/test/packaging.test.mjs +0 -184
- package/test/portability.test.mjs +0 -343
- package/test/rc4-regressions.test.mjs +0 -562
- package/test/rc5-regressions.test.mjs +0 -197
- package/test/rc6-chinese.test.mjs +0 -394
- package/test/router-1100/apply-off.test.mjs +0 -175
- package/test/router-1100/budget-reconcile.test.mjs +0 -215
- package/test/router-1100/config-context.test.mjs +0 -211
- package/test/router-1100/helpers.mjs +0 -129
- package/test/router-1100/hint-envelope.test.mjs +0 -150
- package/test/router-1100/lock-crossproc.test.mjs +0 -206
- package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
- package/test/router-1100/router-apply.test.mjs +0 -372
- package/test/router-1100/router-fixb.test.mjs +0 -602
- package/test/router-1100/router-proposals.test.mjs +0 -405
- package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
- package/test/router-1100/router-security-cr.test.mjs +0 -333
- package/test/router-1100/service-lifecycle.test.mjs +0 -220
- package/test/router-1100/tools-contract.test.mjs +0 -164
- package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
- package/test/router-1100/v6-contract.test.mjs +0 -158
- package/test/router-1100/v6b-contract.test.mjs +0 -293
- package/test/router-1100/v6c-contract.test.mjs +0 -249
- package/test/router-1100/wal-rotation.test.mjs +0 -375
- package/test/router-1100/wp1-config.test.mjs +0 -170
- package/test/router-1100/wp1-default-off.test.mjs +0 -77
- package/test/router-1100/wp1-model-status.test.mjs +0 -90
- package/test/router-1100/wp1-status.test.mjs +0 -220
- package/test/router-1100/wp1-tools.test.mjs +0 -146
- package/test/router-1100/wp5-cli.test.mjs +0 -240
- package/test/router-1100/wp5-model-auto.test.mjs +0 -418
- package/test/router-1100/wp6-merge.test.mjs +0 -386
- package/test/router-1100/wp6-wake.test.mjs +0 -502
- package/test/router-background/audit.test.mjs +0 -37
- package/test/router-background/budget-ledger.test.mjs +0 -330
- package/test/router-background/cross-process.test.mjs +0 -222
- package/test/router-background/fixtures/cross-child.mjs +0 -85
- package/test/router-background/fixtures/real-shapes.mjs +0 -239
- package/test/router-background/router.test.mjs +0 -1076
- package/test/router-background/stream-retry.test.mjs +0 -358
- package/test/router-background/tool-agent.test.mjs +0 -384
- package/test/router-face/bridge.test.mjs +0 -169
- package/test/router-face/face.test.mjs +0 -316
- package/test/router-failclosed.test.mjs +0 -466
- package/test/router-identity.test.mjs +0 -384
- package/test/router-l1-l2.contract.test.mjs +0 -1075
- package/test/router-memory-tools.test.mjs +0 -262
- package/test/router-policy.test.mjs +0 -225
- package/test/router-runtime.test.mjs +0 -438
- package/test/router-shadow.test.mjs +0 -287
- package/test/router.test.mjs +0 -903
- package/test/run-rc.mjs +0 -315
- package/test/run.mjs +0 -2469
- package/test/safety-defaults.test.mjs +0 -181
- package/test/seeded-load.test.mjs +0 -436
- package/test/session-original.test.mjs +0 -338
- package/test/state-truth.test.mjs +0 -937
- package/test/surgery-recovery.test.mjs +0 -936
- package/test/t7_drive.mjs +0 -151
- package/test/tool-output.test.mjs +0 -393
- package/test/tool-schema.test.mjs +0 -177
- package/test/tools_contract.test.mjs +0 -716
- package/test/truncate-visible.test.mjs +0 -1150
- package/test/ui-flags.test.mjs +0 -477
- package/test/v4-hardening.test.mjs +0 -427
- 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
|
-
|