@hasna/mementos 0.15.2 → 0.16.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/README.md +75 -21
- package/dist/audit-contract.d.ts +87 -0
- package/dist/audit-contract.d.ts.map +1 -0
- package/dist/cli/commands/info-context.d.ts.map +1 -1
- package/dist/cli/commands/info-history.d.ts.map +1 -1
- package/dist/cli/commands/io-backup.d.ts.map +1 -1
- package/dist/cli/commands/io-restore.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-list.d.ts +25 -0
- package/dist/cli/commands/memory-cmd-list.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/commands/system-session.d.ts.map +1 -1
- package/dist/cli/commands/system-synthesized-profile.d.ts.map +1 -1
- package/dist/cli/index.js +2093 -740
- package/dist/db/api-mode.d.ts +10 -0
- package/dist/db/api-mode.d.ts.map +1 -1
- package/dist/db/api-response-contract.d.ts +23 -0
- package/dist/db/api-response-contract.d.ts.map +1 -0
- package/dist/db/audit.d.ts +34 -45
- package/dist/db/audit.d.ts.map +1 -1
- package/dist/db/database.d.ts +1 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/locks.d.ts.map +1 -1
- package/dist/db/machines.d.ts +50 -13
- package/dist/db/machines.d.ts.map +1 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrate.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts +2 -0
- package/dist/db/session-jobs.d.ts.map +1 -1
- package/dist/diagnostics/historical-project-registration-receipt.js +215 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1523 -206
- package/dist/lib/built-in-hooks.d.ts.map +1 -1
- package/dist/lib/injection-project.d.ts +10 -0
- package/dist/lib/injection-project.d.ts.map +1 -0
- package/dist/lib/injector.d.ts +1 -0
- package/dist/lib/injector.d.ts.map +1 -1
- package/dist/lib/machine-visibility.d.ts.map +1 -1
- package/dist/lib/memory-lock.d.ts.map +1 -1
- package/dist/lib/profile-synthesizer.d.ts +13 -2
- package/dist/lib/profile-synthesizer.d.ts.map +1 -1
- package/dist/lib/session-queue.d.ts.map +1 -1
- package/dist/lib/session-registry.d.ts +38 -1
- package/dist/lib/session-registry.d.ts.map +1 -1
- package/dist/lib/storage-sync.d.ts +5 -0
- package/dist/lib/storage-sync.d.ts.map +1 -1
- package/dist/lib/synthesis/index.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +3828 -1776
- package/dist/mcp/profile.d.ts +23 -0
- package/dist/mcp/profile.d.ts.map +1 -0
- package/dist/mcp/tools/lock-tools.d.ts.map +1 -1
- package/dist/mcp/tools/memory-audit.d.ts +1 -1
- package/dist/mcp/tools/memory-audit.d.ts.map +1 -1
- package/dist/mcp/tools/memory-crud.d.ts.map +1 -1
- package/dist/mcp/tools/memory-inject.d.ts.map +1 -1
- package/dist/mcp/tools/project-tools.d.ts.map +1 -1
- package/dist/mcp/tools/session-tools.d.ts.map +1 -1
- package/dist/mcp/tools/tool-registry.d.ts +22 -19
- package/dist/mcp/tools/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tools/utility-tools.d.ts +2 -1
- package/dist/mcp/tools/utility-tools.d.ts.map +1 -1
- package/dist/project-registration.js +276 -20
- package/dist/sdk/index.d.ts +109 -6
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +740 -25
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3195 -1134
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/routes/audit.d.ts +2 -0
- package/dist/server/routes/audit.d.ts.map +1 -0
- package/dist/server/routes/machines.d.ts +2 -0
- package/dist/server/routes/machines.d.ts.map +1 -0
- package/dist/server/routes/system-sessions.d.ts.map +1 -1
- package/dist/server/routes/system-synthesis.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/hasna.contract.json +2 -2
- package/package.json +8 -5
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts +0 -2
- package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts.map +0 -1
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts +0 -2
- package/dist/cli/__fixtures__/io-restore-stub-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts +0 -2
- package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts +0 -2
- package/dist/db/__fixtures__/list-filter-capture-server.d.ts.map +0 -1
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts +0 -2
- package/dist/db/__fixtures__/list-filter-client-runner.d.ts.map +0 -1
- package/dist/test-support/memories-page-stub-server.d.ts +0 -2
- package/dist/test-support/memories-page-stub-server.d.ts.map +0 -1
- package/dist/test-support/memories-page-stub.d.ts +0 -28
- package/dist/test-support/memories-page-stub.d.ts.map +0 -1
- package/dist/test-support/pg-sync-stub-worker.d.ts +0 -2
- package/dist/test-support/pg-sync-stub-worker.d.ts.map +0 -1
- package/dist/test-support/preload-local-store.d.ts +0 -2
- package/dist/test-support/preload-local-store.d.ts.map +0 -1
- package/dist/test-support/project-authority-identity.d.ts +0 -8
- package/dist/test-support/project-authority-identity.d.ts.map +0 -1
- package/dist/test-support/store-isolation.d.ts +0 -147
- package/dist/test-support/store-isolation.d.ts.map +0 -1
- package/dist/test-support/strip-ansi.d.ts +0 -3
- package/dist/test-support/strip-ansi.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1239,6 +1239,8 @@ function getApiConfig(env2 = process.env, options = {}) {
|
|
|
1239
1239
|
function isApiMode() {
|
|
1240
1240
|
if (hasDatabaseUrl())
|
|
1241
1241
|
return false;
|
|
1242
|
+
if (isServerContext())
|
|
1243
|
+
return false;
|
|
1242
1244
|
return getApiConfig() !== null;
|
|
1243
1245
|
}
|
|
1244
1246
|
function assertClientStoreConfigured(env2 = process.env, options = {}) {
|
|
@@ -1285,7 +1287,14 @@ x-api-key: ${cfg.apiKey}
|
|
|
1285
1287
|
let bodyFile;
|
|
1286
1288
|
if (hasBody) {
|
|
1287
1289
|
bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
|
|
1288
|
-
|
|
1290
|
+
try {
|
|
1291
|
+
writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
try {
|
|
1294
|
+
unlinkSync(bodyFile);
|
|
1295
|
+
} catch {}
|
|
1296
|
+
throw error;
|
|
1297
|
+
}
|
|
1289
1298
|
args.push("--data-binary", `@${bodyFile}`);
|
|
1290
1299
|
}
|
|
1291
1300
|
args.push(url);
|
|
@@ -1333,7 +1342,7 @@ function apiJson(method, path, body, options) {
|
|
|
1333
1342
|
try {
|
|
1334
1343
|
data = JSON.parse(raw.body);
|
|
1335
1344
|
} catch (e) {
|
|
1336
|
-
throw new ApiRequestError(`mementos cloud ${method} ${path} returned status ${raw.status} with a body that is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 the response is truncated or the server is unhealthy`, raw.status,
|
|
1345
|
+
throw new ApiRequestError(`mementos cloud ${method} ${path} returned status ${raw.status} with a body that is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 the response is truncated or the server is unhealthy`, raw.status, "");
|
|
1337
1346
|
}
|
|
1338
1347
|
} else {
|
|
1339
1348
|
data = undefined;
|
|
@@ -1375,6 +1384,7 @@ function toQuery(params) {
|
|
|
1375
1384
|
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, DB_PATH_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
1376
1385
|
var init_api_mode = __esm(() => {
|
|
1377
1386
|
init_local_opt_in();
|
|
1387
|
+
init_storage();
|
|
1378
1388
|
API_URL_ENV_KEYS = clientTransportEnvKeys2("mementos").apiUrlKeys;
|
|
1379
1389
|
API_KEY_ENV_KEYS = clientTransportEnvKeys2("mementos").apiKeyKeys;
|
|
1380
1390
|
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
@@ -2787,6 +2797,220 @@ INSERT OR IGNORE INTO _migrations (id) VALUES (38);
|
|
|
2787
2797
|
`
|
|
2788
2798
|
${sqliteMementosMemoryProjectLinkSchemaSql()}
|
|
2789
2799
|
INSERT OR IGNORE INTO _migrations (id) VALUES (40);
|
|
2800
|
+
`,
|
|
2801
|
+
`
|
|
2802
|
+
DROP TABLE IF EXISTS temp.mementos_m41_machine_preflight;
|
|
2803
|
+
CREATE TEMP TABLE mementos_m41_machine_preflight (
|
|
2804
|
+
id TEXT PRIMARY KEY,
|
|
2805
|
+
name TEXT NOT NULL CHECK(
|
|
2806
|
+
length(name) BETWEEN 1 AND 128
|
|
2807
|
+
AND instr(name, char(0)) = 0
|
|
2808
|
+
AND instr(name, char(1)) = 0
|
|
2809
|
+
AND instr(name, char(2)) = 0
|
|
2810
|
+
AND instr(name, char(3)) = 0
|
|
2811
|
+
AND instr(name, char(4)) = 0
|
|
2812
|
+
AND instr(name, char(5)) = 0
|
|
2813
|
+
AND instr(name, char(6)) = 0
|
|
2814
|
+
AND instr(name, char(7)) = 0
|
|
2815
|
+
AND instr(name, char(8)) = 0
|
|
2816
|
+
AND instr(name, char(9)) = 0
|
|
2817
|
+
AND instr(name, char(10)) = 0
|
|
2818
|
+
AND instr(name, char(11)) = 0
|
|
2819
|
+
AND instr(name, char(12)) = 0
|
|
2820
|
+
AND instr(name, char(13)) = 0
|
|
2821
|
+
AND instr(name, char(14)) = 0
|
|
2822
|
+
AND instr(name, char(15)) = 0
|
|
2823
|
+
AND instr(name, char(16)) = 0
|
|
2824
|
+
AND instr(name, char(17)) = 0
|
|
2825
|
+
AND instr(name, char(18)) = 0
|
|
2826
|
+
AND instr(name, char(19)) = 0
|
|
2827
|
+
AND instr(name, char(20)) = 0
|
|
2828
|
+
AND instr(name, char(21)) = 0
|
|
2829
|
+
AND instr(name, char(22)) = 0
|
|
2830
|
+
AND instr(name, char(23)) = 0
|
|
2831
|
+
AND instr(name, char(24)) = 0
|
|
2832
|
+
AND instr(name, char(25)) = 0
|
|
2833
|
+
AND instr(name, char(26)) = 0
|
|
2834
|
+
AND instr(name, char(27)) = 0
|
|
2835
|
+
AND instr(name, char(28)) = 0
|
|
2836
|
+
AND instr(name, char(29)) = 0
|
|
2837
|
+
AND instr(name, char(30)) = 0
|
|
2838
|
+
AND instr(name, char(31)) = 0
|
|
2839
|
+
AND instr(name, char(127)) = 0
|
|
2840
|
+
),
|
|
2841
|
+
hostname TEXT NOT NULL UNIQUE CHECK(
|
|
2842
|
+
length(hostname) BETWEEN 1 AND 253
|
|
2843
|
+
AND hostname = lower(rtrim(trim(hostname), '.'))
|
|
2844
|
+
AND instr(hostname, ' ') = 0
|
|
2845
|
+
AND instr(hostname, char(9)) = 0
|
|
2846
|
+
AND instr(hostname, char(10)) = 0
|
|
2847
|
+
AND instr(hostname, char(13)) = 0
|
|
2848
|
+
AND instr(hostname, '/') = 0
|
|
2849
|
+
AND instr(hostname, char(92)) = 0
|
|
2850
|
+
),
|
|
2851
|
+
platform TEXT NOT NULL CHECK(
|
|
2852
|
+
length(platform) BETWEEN 1 AND 64
|
|
2853
|
+
AND platform = lower(trim(platform))
|
|
2854
|
+
AND platform NOT GLOB '*[^a-z0-9._-]*'
|
|
2855
|
+
),
|
|
2856
|
+
is_primary INTEGER NOT NULL CHECK(is_primary IN (0, 1)),
|
|
2857
|
+
created_at TEXT NOT NULL CHECK(julianday(created_at) IS NOT NULL),
|
|
2858
|
+
last_seen_at TEXT NOT NULL CHECK(
|
|
2859
|
+
julianday(last_seen_at) IS NOT NULL
|
|
2860
|
+
AND julianday(last_seen_at) >= julianday(created_at)
|
|
2861
|
+
)
|
|
2862
|
+
);
|
|
2863
|
+
INSERT INTO mementos_m41_machine_preflight
|
|
2864
|
+
(id, name, hostname, platform, is_primary, created_at, last_seen_at)
|
|
2865
|
+
SELECT
|
|
2866
|
+
id,
|
|
2867
|
+
name,
|
|
2868
|
+
lower(rtrim(trim(hostname), '.')),
|
|
2869
|
+
platform,
|
|
2870
|
+
is_primary,
|
|
2871
|
+
created_at,
|
|
2872
|
+
last_seen_at
|
|
2873
|
+
FROM machines;
|
|
2874
|
+
|
|
2875
|
+
UPDATE machines
|
|
2876
|
+
SET hostname = (
|
|
2877
|
+
SELECT preflight.hostname
|
|
2878
|
+
FROM mementos_m41_machine_preflight preflight
|
|
2879
|
+
WHERE preflight.id = machines.id
|
|
2880
|
+
);
|
|
2881
|
+
|
|
2882
|
+
DROP TABLE mementos_m41_machine_preflight;
|
|
2883
|
+
DROP TRIGGER IF EXISTS machines_single_primary_insert;
|
|
2884
|
+
DROP TRIGGER IF EXISTS machines_single_primary_update;
|
|
2885
|
+
DROP INDEX IF EXISTS idx_machines_hostname;
|
|
2886
|
+
CREATE UNIQUE INDEX idx_machines_hostname ON machines(hostname);
|
|
2887
|
+
DROP INDEX IF EXISTS idx_machines_single_primary;
|
|
2888
|
+
CREATE UNIQUE INDEX idx_machines_single_primary
|
|
2889
|
+
ON machines(is_primary) WHERE is_primary = 1;
|
|
2890
|
+
|
|
2891
|
+
CREATE TRIGGER IF NOT EXISTS machines_hostname_canonical_insert
|
|
2892
|
+
BEFORE INSERT ON machines
|
|
2893
|
+
WHEN NEW.hostname != lower(rtrim(trim(NEW.hostname), '.'))
|
|
2894
|
+
OR length(NEW.hostname) NOT BETWEEN 1 AND 253
|
|
2895
|
+
OR instr(NEW.hostname, ' ') > 0
|
|
2896
|
+
OR instr(NEW.hostname, char(9)) > 0
|
|
2897
|
+
OR instr(NEW.hostname, char(10)) > 0
|
|
2898
|
+
OR instr(NEW.hostname, char(13)) > 0
|
|
2899
|
+
OR instr(NEW.hostname, '/') > 0
|
|
2900
|
+
OR instr(NEW.hostname, char(92)) > 0
|
|
2901
|
+
BEGIN
|
|
2902
|
+
SELECT RAISE(ABORT, 'machines.hostname must be canonical');
|
|
2903
|
+
END;
|
|
2904
|
+
|
|
2905
|
+
CREATE TRIGGER IF NOT EXISTS machines_hostname_canonical_update
|
|
2906
|
+
BEFORE UPDATE OF hostname ON machines
|
|
2907
|
+
WHEN NEW.hostname != lower(rtrim(trim(NEW.hostname), '.'))
|
|
2908
|
+
OR length(NEW.hostname) NOT BETWEEN 1 AND 253
|
|
2909
|
+
OR instr(NEW.hostname, ' ') > 0
|
|
2910
|
+
OR instr(NEW.hostname, char(9)) > 0
|
|
2911
|
+
OR instr(NEW.hostname, char(10)) > 0
|
|
2912
|
+
OR instr(NEW.hostname, char(13)) > 0
|
|
2913
|
+
OR instr(NEW.hostname, '/') > 0
|
|
2914
|
+
OR instr(NEW.hostname, char(92)) > 0
|
|
2915
|
+
BEGIN
|
|
2916
|
+
SELECT RAISE(ABORT, 'machines.hostname must be canonical');
|
|
2917
|
+
END;
|
|
2918
|
+
|
|
2919
|
+
CREATE TRIGGER IF NOT EXISTS machines_runtime_contract_insert
|
|
2920
|
+
BEFORE INSERT ON machines
|
|
2921
|
+
WHEN length(NEW.name) NOT BETWEEN 1 AND 128
|
|
2922
|
+
OR instr(NEW.name, char(0)) > 0
|
|
2923
|
+
OR instr(NEW.name, char(1)) > 0
|
|
2924
|
+
OR instr(NEW.name, char(2)) > 0
|
|
2925
|
+
OR instr(NEW.name, char(3)) > 0
|
|
2926
|
+
OR instr(NEW.name, char(4)) > 0
|
|
2927
|
+
OR instr(NEW.name, char(5)) > 0
|
|
2928
|
+
OR instr(NEW.name, char(6)) > 0
|
|
2929
|
+
OR instr(NEW.name, char(7)) > 0
|
|
2930
|
+
OR instr(NEW.name, char(8)) > 0
|
|
2931
|
+
OR instr(NEW.name, char(9)) > 0
|
|
2932
|
+
OR instr(NEW.name, char(10)) > 0
|
|
2933
|
+
OR instr(NEW.name, char(11)) > 0
|
|
2934
|
+
OR instr(NEW.name, char(12)) > 0
|
|
2935
|
+
OR instr(NEW.name, char(13)) > 0
|
|
2936
|
+
OR instr(NEW.name, char(14)) > 0
|
|
2937
|
+
OR instr(NEW.name, char(15)) > 0
|
|
2938
|
+
OR instr(NEW.name, char(16)) > 0
|
|
2939
|
+
OR instr(NEW.name, char(17)) > 0
|
|
2940
|
+
OR instr(NEW.name, char(18)) > 0
|
|
2941
|
+
OR instr(NEW.name, char(19)) > 0
|
|
2942
|
+
OR instr(NEW.name, char(20)) > 0
|
|
2943
|
+
OR instr(NEW.name, char(21)) > 0
|
|
2944
|
+
OR instr(NEW.name, char(22)) > 0
|
|
2945
|
+
OR instr(NEW.name, char(23)) > 0
|
|
2946
|
+
OR instr(NEW.name, char(24)) > 0
|
|
2947
|
+
OR instr(NEW.name, char(25)) > 0
|
|
2948
|
+
OR instr(NEW.name, char(26)) > 0
|
|
2949
|
+
OR instr(NEW.name, char(27)) > 0
|
|
2950
|
+
OR instr(NEW.name, char(28)) > 0
|
|
2951
|
+
OR instr(NEW.name, char(29)) > 0
|
|
2952
|
+
OR instr(NEW.name, char(30)) > 0
|
|
2953
|
+
OR instr(NEW.name, char(31)) > 0
|
|
2954
|
+
OR instr(NEW.name, char(127)) > 0
|
|
2955
|
+
OR length(NEW.platform) NOT BETWEEN 1 AND 64
|
|
2956
|
+
OR NEW.platform != lower(trim(NEW.platform))
|
|
2957
|
+
OR NEW.platform GLOB '*[^a-z0-9._-]*'
|
|
2958
|
+
OR NEW.is_primary NOT IN (0, 1)
|
|
2959
|
+
OR julianday(NEW.created_at) IS NULL
|
|
2960
|
+
OR julianday(NEW.last_seen_at) IS NULL
|
|
2961
|
+
OR julianday(NEW.last_seen_at) < julianday(NEW.created_at)
|
|
2962
|
+
BEGIN
|
|
2963
|
+
SELECT RAISE(ABORT, 'machines row violates the persisted runtime contract');
|
|
2964
|
+
END;
|
|
2965
|
+
|
|
2966
|
+
CREATE TRIGGER IF NOT EXISTS machines_runtime_contract_update
|
|
2967
|
+
BEFORE UPDATE OF name, platform, is_primary, created_at, last_seen_at ON machines
|
|
2968
|
+
WHEN length(NEW.name) NOT BETWEEN 1 AND 128
|
|
2969
|
+
OR instr(NEW.name, char(0)) > 0
|
|
2970
|
+
OR instr(NEW.name, char(1)) > 0
|
|
2971
|
+
OR instr(NEW.name, char(2)) > 0
|
|
2972
|
+
OR instr(NEW.name, char(3)) > 0
|
|
2973
|
+
OR instr(NEW.name, char(4)) > 0
|
|
2974
|
+
OR instr(NEW.name, char(5)) > 0
|
|
2975
|
+
OR instr(NEW.name, char(6)) > 0
|
|
2976
|
+
OR instr(NEW.name, char(7)) > 0
|
|
2977
|
+
OR instr(NEW.name, char(8)) > 0
|
|
2978
|
+
OR instr(NEW.name, char(9)) > 0
|
|
2979
|
+
OR instr(NEW.name, char(10)) > 0
|
|
2980
|
+
OR instr(NEW.name, char(11)) > 0
|
|
2981
|
+
OR instr(NEW.name, char(12)) > 0
|
|
2982
|
+
OR instr(NEW.name, char(13)) > 0
|
|
2983
|
+
OR instr(NEW.name, char(14)) > 0
|
|
2984
|
+
OR instr(NEW.name, char(15)) > 0
|
|
2985
|
+
OR instr(NEW.name, char(16)) > 0
|
|
2986
|
+
OR instr(NEW.name, char(17)) > 0
|
|
2987
|
+
OR instr(NEW.name, char(18)) > 0
|
|
2988
|
+
OR instr(NEW.name, char(19)) > 0
|
|
2989
|
+
OR instr(NEW.name, char(20)) > 0
|
|
2990
|
+
OR instr(NEW.name, char(21)) > 0
|
|
2991
|
+
OR instr(NEW.name, char(22)) > 0
|
|
2992
|
+
OR instr(NEW.name, char(23)) > 0
|
|
2993
|
+
OR instr(NEW.name, char(24)) > 0
|
|
2994
|
+
OR instr(NEW.name, char(25)) > 0
|
|
2995
|
+
OR instr(NEW.name, char(26)) > 0
|
|
2996
|
+
OR instr(NEW.name, char(27)) > 0
|
|
2997
|
+
OR instr(NEW.name, char(28)) > 0
|
|
2998
|
+
OR instr(NEW.name, char(29)) > 0
|
|
2999
|
+
OR instr(NEW.name, char(30)) > 0
|
|
3000
|
+
OR instr(NEW.name, char(31)) > 0
|
|
3001
|
+
OR instr(NEW.name, char(127)) > 0
|
|
3002
|
+
OR length(NEW.platform) NOT BETWEEN 1 AND 64
|
|
3003
|
+
OR NEW.platform != lower(trim(NEW.platform))
|
|
3004
|
+
OR NEW.platform GLOB '*[^a-z0-9._-]*'
|
|
3005
|
+
OR NEW.is_primary NOT IN (0, 1)
|
|
3006
|
+
OR julianday(NEW.created_at) IS NULL
|
|
3007
|
+
OR julianday(NEW.last_seen_at) IS NULL
|
|
3008
|
+
OR julianday(NEW.last_seen_at) < julianday(NEW.created_at)
|
|
3009
|
+
BEGIN
|
|
3010
|
+
SELECT RAISE(ABORT, 'machines row violates the persisted runtime contract');
|
|
3011
|
+
END;
|
|
3012
|
+
|
|
3013
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (41);
|
|
2790
3014
|
`
|
|
2791
3015
|
];
|
|
2792
3016
|
});
|
|
@@ -2802,7 +3026,8 @@ __export(exports_database, {
|
|
|
2802
3026
|
getDbPath: () => getDbPath,
|
|
2803
3027
|
getDatabase: () => getDatabase,
|
|
2804
3028
|
escapeLikePrefix: () => escapeLikePrefix,
|
|
2805
|
-
closeDatabase: () => closeDatabase
|
|
3029
|
+
closeDatabase: () => closeDatabase,
|
|
3030
|
+
applyMachineIdentityMigration: () => applyMachineIdentityMigration
|
|
2806
3031
|
});
|
|
2807
3032
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
2808
3033
|
import { dirname, join as join4, resolve as resolve2 } from "path";
|
|
@@ -2930,32 +3155,62 @@ function getDatabase(dbPath) {
|
|
|
2930
3155
|
return _db;
|
|
2931
3156
|
}
|
|
2932
3157
|
function runMigrations(db) {
|
|
3158
|
+
let currentLevel = 0;
|
|
2933
3159
|
try {
|
|
2934
3160
|
const result = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
for (let i = 0;i < MIGRATIONS.length; i++) {
|
|
2945
|
-
try {
|
|
2946
|
-
applyMigration(db, i);
|
|
2947
|
-
} catch (e) {
|
|
2948
|
-
console.warn(`[mementos] Migration ${i + 1} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
3161
|
+
currentLevel = result?.max_id ?? 0;
|
|
3162
|
+
} catch {}
|
|
3163
|
+
for (let i = currentLevel;i < MIGRATIONS.length; i++) {
|
|
3164
|
+
try {
|
|
3165
|
+
applyMigration(db, i);
|
|
3166
|
+
} catch (error) {
|
|
3167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3168
|
+
if (i === 40) {
|
|
3169
|
+
throw new Error(`Mementos migration 41 refused unsafe machine identity data: ${message}`);
|
|
2949
3170
|
}
|
|
3171
|
+
console.warn(`[mementos] Migration ${i + 1} failed: ${message}`);
|
|
2950
3172
|
}
|
|
2951
3173
|
}
|
|
2952
3174
|
}
|
|
2953
3175
|
function applyMigration(db, index) {
|
|
2954
|
-
if (index === 32)
|
|
3176
|
+
if (index === 32)
|
|
2955
3177
|
ensureMachinePrimaryColumn(db);
|
|
3178
|
+
if (index === 40) {
|
|
3179
|
+
applyMachineIdentityMigration(db);
|
|
3180
|
+
return;
|
|
2956
3181
|
}
|
|
2957
3182
|
db.exec(MIGRATIONS[index]);
|
|
2958
3183
|
}
|
|
3184
|
+
function applyMachineIdentityMigration(db) {
|
|
3185
|
+
db.transaction(() => {
|
|
3186
|
+
const rows = db.query("SELECT id, name, hostname, platform, is_primary, created_at, last_seen_at FROM machines").all();
|
|
3187
|
+
const identities = new Map;
|
|
3188
|
+
let primaryCount = 0;
|
|
3189
|
+
for (const row of rows) {
|
|
3190
|
+
const canonical = row.hostname.trim().replace(/\.+$/, "").toLowerCase();
|
|
3191
|
+
const createdAt = new Date(row.created_at).getTime();
|
|
3192
|
+
const lastSeenAt = new Date(row.last_seen_at).getTime();
|
|
3193
|
+
const primaryValueValid = row.is_primary === 0 || row.is_primary === 1 || row.is_primary === false || row.is_primary === true;
|
|
3194
|
+
if (!row.id || !row.name || row.name.length > 128 || /[\u0000-\u001f\u007f]/.test(row.name) || !canonical || canonical.length > 253 || /[\u0000-\u001f\u007f/\\\s]/.test(canonical) || !row.platform || row.platform.length > 64 || row.platform !== row.platform.trim().toLowerCase() || !/^[a-z0-9._-]+$/.test(row.platform) || !primaryValueValid || !Number.isFinite(createdAt) || !Number.isFinite(lastSeenAt) || lastSeenAt < createdAt) {
|
|
3195
|
+
throw new Error(`invalid legacy machine row ${row.id || "(missing id)"}`);
|
|
3196
|
+
}
|
|
3197
|
+
const owner = identities.get(canonical);
|
|
3198
|
+
if (owner && owner !== row.id) {
|
|
3199
|
+
throw new Error(`ambiguous normalized hostname '${canonical}' belongs to both ${owner} and ${row.id}`);
|
|
3200
|
+
}
|
|
3201
|
+
identities.set(canonical, row.id);
|
|
3202
|
+
if (Boolean(row.is_primary))
|
|
3203
|
+
primaryCount += 1;
|
|
3204
|
+
}
|
|
3205
|
+
if (primaryCount > 1) {
|
|
3206
|
+
throw new Error(`legacy machine registry contains ${primaryCount} primary rows`);
|
|
3207
|
+
}
|
|
3208
|
+
db.exec(MIGRATIONS[40]);
|
|
3209
|
+
const receipt = db.query("SELECT id FROM _migrations WHERE id = 41").get();
|
|
3210
|
+
if (receipt?.id !== 41)
|
|
3211
|
+
throw new Error("machine identity migration did not record completion");
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
2959
3214
|
function ensureMachinePrimaryColumn(db) {
|
|
2960
3215
|
const columns = db.query("PRAGMA table_info(machines)").all();
|
|
2961
3216
|
if (columns.length === 0 || columns.some((column) => column.name === "is_primary")) {
|
|
@@ -3100,10 +3355,10 @@ var init_util = __esm(() => {
|
|
|
3100
3355
|
return obj[e];
|
|
3101
3356
|
});
|
|
3102
3357
|
};
|
|
3103
|
-
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (
|
|
3358
|
+
util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object2) => {
|
|
3104
3359
|
const keys = [];
|
|
3105
|
-
for (const key in
|
|
3106
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
3360
|
+
for (const key in object2) {
|
|
3361
|
+
if (Object.prototype.hasOwnProperty.call(object2, key)) {
|
|
3107
3362
|
keys.push(key);
|
|
3108
3363
|
}
|
|
3109
3364
|
}
|
|
@@ -7457,19 +7712,19 @@ function floatSafeRemainder2(val, step) {
|
|
|
7457
7712
|
const stepInt = Number.parseInt(step.toFixed(decCount).replace(".", ""));
|
|
7458
7713
|
return valInt % stepInt / 10 ** decCount;
|
|
7459
7714
|
}
|
|
7460
|
-
function defineLazy(
|
|
7715
|
+
function defineLazy(object2, key, getter) {
|
|
7461
7716
|
const set = false;
|
|
7462
|
-
Object.defineProperty(
|
|
7717
|
+
Object.defineProperty(object2, key, {
|
|
7463
7718
|
get() {
|
|
7464
7719
|
if (!set) {
|
|
7465
7720
|
const value = getter();
|
|
7466
|
-
|
|
7721
|
+
object2[key] = value;
|
|
7467
7722
|
return value;
|
|
7468
7723
|
}
|
|
7469
7724
|
throw new Error("cached value already set");
|
|
7470
7725
|
},
|
|
7471
7726
|
set(v) {
|
|
7472
|
-
Object.defineProperty(
|
|
7727
|
+
Object.defineProperty(object2, key, {
|
|
7473
7728
|
value: v
|
|
7474
7729
|
});
|
|
7475
7730
|
},
|
|
@@ -8085,7 +8340,7 @@ __export(exports_regexes, {
|
|
|
8085
8340
|
undefined: () => _undefined,
|
|
8086
8341
|
ulid: () => ulid,
|
|
8087
8342
|
time: () => time,
|
|
8088
|
-
string: () =>
|
|
8343
|
+
string: () => string2,
|
|
8089
8344
|
rfc5322Email: () => rfc5322Email,
|
|
8090
8345
|
number: () => number,
|
|
8091
8346
|
null: () => _null,
|
|
@@ -8094,7 +8349,7 @@ __export(exports_regexes, {
|
|
|
8094
8349
|
ksuid: () => ksuid,
|
|
8095
8350
|
ipv6: () => ipv6,
|
|
8096
8351
|
ipv4: () => ipv4,
|
|
8097
|
-
integer: () =>
|
|
8352
|
+
integer: () => integer2,
|
|
8098
8353
|
html5Email: () => html5Email,
|
|
8099
8354
|
hostname: () => hostname2,
|
|
8100
8355
|
guid: () => guid,
|
|
@@ -8111,7 +8366,7 @@ __export(exports_regexes, {
|
|
|
8111
8366
|
cidrv6: () => cidrv6,
|
|
8112
8367
|
cidrv4: () => cidrv4,
|
|
8113
8368
|
browserEmail: () => browserEmail,
|
|
8114
|
-
boolean: () =>
|
|
8369
|
+
boolean: () => boolean2,
|
|
8115
8370
|
bigint: () => bigint,
|
|
8116
8371
|
base64url: () => base64url,
|
|
8117
8372
|
base64: () => base64,
|
|
@@ -8142,10 +8397,10 @@ var cuid, cuid2, ulid, xid, ksuid, nanoid, duration, extendedDuration, guid, uui
|
|
|
8142
8397
|
if (!version)
|
|
8143
8398
|
return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000)$/;
|
|
8144
8399
|
return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
|
|
8145
|
-
}, uuid4, uuid6, uuid7, email, html5Email, rfc5322Email, unicodeEmail, browserEmail, _emoji = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`, ipv4, ipv6, cidrv4, cidrv6, base64, base64url, hostname2, domain, e164, dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`, date,
|
|
8400
|
+
}, uuid4, uuid6, uuid7, email, html5Email, rfc5322Email, unicodeEmail, browserEmail, _emoji = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`, ipv4, ipv6, cidrv4, cidrv6, base64, base64url, hostname2, domain, e164, dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`, date, string2 = (params) => {
|
|
8146
8401
|
const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
|
|
8147
8402
|
return new RegExp(`^${regex}$`);
|
|
8148
|
-
}, bigint,
|
|
8403
|
+
}, bigint, integer2, number, boolean2, _null, _undefined, lowercase, uppercase;
|
|
8149
8404
|
var init_regexes = __esm(() => {
|
|
8150
8405
|
cuid = /^[cC][^\s-]{8,}$/;
|
|
8151
8406
|
cuid2 = /^[0-9a-z]+$/;
|
|
@@ -8175,9 +8430,9 @@ var init_regexes = __esm(() => {
|
|
|
8175
8430
|
e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
|
|
8176
8431
|
date = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
|
|
8177
8432
|
bigint = /^\d+n?$/;
|
|
8178
|
-
|
|
8433
|
+
integer2 = /^\d+$/;
|
|
8179
8434
|
number = /^-?\d+(?:\.\d+)?/i;
|
|
8180
|
-
|
|
8435
|
+
boolean2 = /true|false/i;
|
|
8181
8436
|
_null = /null/i;
|
|
8182
8437
|
_undefined = /undefined/i;
|
|
8183
8438
|
lowercase = /^[^A-Z]*$/;
|
|
@@ -8296,7 +8551,7 @@ var init_checks = __esm(() => {
|
|
|
8296
8551
|
bag.minimum = minimum;
|
|
8297
8552
|
bag.maximum = maximum;
|
|
8298
8553
|
if (isInt)
|
|
8299
|
-
bag.pattern =
|
|
8554
|
+
bag.pattern = integer2;
|
|
8300
8555
|
});
|
|
8301
8556
|
inst._zod.check = (payload) => {
|
|
8302
8557
|
const input = payload.value;
|
|
@@ -9098,7 +9353,7 @@ var init_schemas = __esm(() => {
|
|
|
9098
9353
|
});
|
|
9099
9354
|
$ZodString = /* @__PURE__ */ $constructor("$ZodString", (inst, def) => {
|
|
9100
9355
|
$ZodType.init(inst, def);
|
|
9101
|
-
inst._zod.pattern = [...inst?._zod.bag?.patterns ?? []].pop() ??
|
|
9356
|
+
inst._zod.pattern = [...inst?._zod.bag?.patterns ?? []].pop() ?? string2(inst._zod.bag);
|
|
9102
9357
|
inst._zod.parse = (payload, _) => {
|
|
9103
9358
|
if (def.coerce)
|
|
9104
9359
|
try {
|
|
@@ -9398,7 +9653,7 @@ var init_schemas = __esm(() => {
|
|
|
9398
9653
|
});
|
|
9399
9654
|
$ZodBoolean = /* @__PURE__ */ $constructor("$ZodBoolean", (inst, def) => {
|
|
9400
9655
|
$ZodType.init(inst, def);
|
|
9401
|
-
inst._zod.pattern =
|
|
9656
|
+
inst._zod.pattern = boolean2;
|
|
9402
9657
|
inst._zod.parse = (payload, _ctx) => {
|
|
9403
9658
|
if (def.coerce)
|
|
9404
9659
|
try {
|
|
@@ -17422,7 +17677,7 @@ var init_parse2 = __esm(() => {
|
|
|
17422
17677
|
});
|
|
17423
17678
|
|
|
17424
17679
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v4/classic/schemas.js
|
|
17425
|
-
function
|
|
17680
|
+
function string3(params) {
|
|
17426
17681
|
return _string(ZodString2, params);
|
|
17427
17682
|
}
|
|
17428
17683
|
function email2(params) {
|
|
@@ -17512,7 +17767,7 @@ function int32(params) {
|
|
|
17512
17767
|
function uint32(params) {
|
|
17513
17768
|
return _uint32(ZodNumberFormat, params);
|
|
17514
17769
|
}
|
|
17515
|
-
function
|
|
17770
|
+
function boolean3(params) {
|
|
17516
17771
|
return _boolean(ZodBoolean2, params);
|
|
17517
17772
|
}
|
|
17518
17773
|
function bigint2(params) {
|
|
@@ -17555,7 +17810,7 @@ function keyof(schema) {
|
|
|
17555
17810
|
const shape = schema._zod.def.shape;
|
|
17556
17811
|
return literal(Object.keys(shape));
|
|
17557
17812
|
}
|
|
17558
|
-
function
|
|
17813
|
+
function object2(shape, params) {
|
|
17559
17814
|
const def = {
|
|
17560
17815
|
type: "object",
|
|
17561
17816
|
get shape() {
|
|
@@ -17621,7 +17876,7 @@ function tuple(items, _paramsOrRest, _params) {
|
|
|
17621
17876
|
...exports_util.normalizeParams(params)
|
|
17622
17877
|
});
|
|
17623
17878
|
}
|
|
17624
|
-
function
|
|
17879
|
+
function record2(keyType, valueType, params) {
|
|
17625
17880
|
return new ZodRecord2({
|
|
17626
17881
|
type: "record",
|
|
17627
17882
|
keyType,
|
|
@@ -17819,7 +18074,7 @@ function _instanceof(cls, params = {
|
|
|
17819
18074
|
}
|
|
17820
18075
|
function json2(params) {
|
|
17821
18076
|
const jsonSchema = lazy(() => {
|
|
17822
|
-
return union([
|
|
18077
|
+
return union([string3(params), number2(), boolean3(), _null3(), array(jsonSchema), record2(string3(), jsonSchema)]);
|
|
17823
18078
|
});
|
|
17824
18079
|
return jsonSchema;
|
|
17825
18080
|
}
|
|
@@ -18390,19 +18645,19 @@ var init_compat = __esm(() => {
|
|
|
18390
18645
|
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v4/classic/coerce.js
|
|
18391
18646
|
var exports_coerce = {};
|
|
18392
18647
|
__export(exports_coerce, {
|
|
18393
|
-
string: () =>
|
|
18648
|
+
string: () => string4,
|
|
18394
18649
|
number: () => number3,
|
|
18395
18650
|
date: () => date4,
|
|
18396
|
-
boolean: () =>
|
|
18651
|
+
boolean: () => boolean4,
|
|
18397
18652
|
bigint: () => bigint3
|
|
18398
18653
|
});
|
|
18399
|
-
function
|
|
18654
|
+
function string4(params) {
|
|
18400
18655
|
return _coercedString(ZodString2, params);
|
|
18401
18656
|
}
|
|
18402
18657
|
function number3(params) {
|
|
18403
18658
|
return _coercedNumber(ZodNumber2, params);
|
|
18404
18659
|
}
|
|
18405
|
-
function
|
|
18660
|
+
function boolean4(params) {
|
|
18406
18661
|
return _coercedBoolean(ZodBoolean2, params);
|
|
18407
18662
|
}
|
|
18408
18663
|
function bigint3(params) {
|
|
@@ -18446,7 +18701,7 @@ __export(exports_external2, {
|
|
|
18446
18701
|
success: () => success,
|
|
18447
18702
|
stringbool: () => stringbool,
|
|
18448
18703
|
stringFormat: () => stringFormat,
|
|
18449
|
-
string: () =>
|
|
18704
|
+
string: () => string3,
|
|
18450
18705
|
strictObject: () => strictObject,
|
|
18451
18706
|
startsWith: () => _startsWith,
|
|
18452
18707
|
size: () => _size,
|
|
@@ -18458,7 +18713,7 @@ __export(exports_external2, {
|
|
|
18458
18713
|
regexes: () => exports_regexes,
|
|
18459
18714
|
regex: () => _regex,
|
|
18460
18715
|
refine: () => refine,
|
|
18461
|
-
record: () =>
|
|
18716
|
+
record: () => record2,
|
|
18462
18717
|
readonly: () => readonly,
|
|
18463
18718
|
property: () => _property,
|
|
18464
18719
|
promise: () => promise,
|
|
@@ -18472,7 +18727,7 @@ __export(exports_external2, {
|
|
|
18472
18727
|
parse: () => parse3,
|
|
18473
18728
|
overwrite: () => _overwrite,
|
|
18474
18729
|
optional: () => optional,
|
|
18475
|
-
object: () =>
|
|
18730
|
+
object: () => object2,
|
|
18476
18731
|
number: () => number2,
|
|
18477
18732
|
nullish: () => nullish2,
|
|
18478
18733
|
nullable: () => nullable,
|
|
@@ -18543,7 +18798,7 @@ __export(exports_external2, {
|
|
|
18543
18798
|
cidrv4: () => cidrv42,
|
|
18544
18799
|
check: () => check,
|
|
18545
18800
|
catch: () => _catch2,
|
|
18546
|
-
boolean: () =>
|
|
18801
|
+
boolean: () => boolean3,
|
|
18547
18802
|
bigint: () => bigint2,
|
|
18548
18803
|
base64url: () => base64url2,
|
|
18549
18804
|
base64: () => base642,
|
|
@@ -20056,11 +20311,11 @@ function parseMapDef(def, refs) {
|
|
|
20056
20311
|
};
|
|
20057
20312
|
}
|
|
20058
20313
|
function parseNativeEnumDef(def) {
|
|
20059
|
-
const
|
|
20314
|
+
const object3 = def.values;
|
|
20060
20315
|
const actualKeys = Object.keys(def.values).filter((key) => {
|
|
20061
|
-
return typeof
|
|
20316
|
+
return typeof object3[object3[key]] !== "number";
|
|
20062
20317
|
});
|
|
20063
|
-
const actualValues = actualKeys.map((key) =>
|
|
20318
|
+
const actualValues = actualKeys.map((key) => object3[key]);
|
|
20064
20319
|
const parsedTypes = Array.from(new Set(actualValues.map((values) => typeof values)));
|
|
20065
20320
|
return {
|
|
20066
20321
|
type: parsedTypes.length === 1 ? parsedTypes[0] === "string" ? "string" : "number" : ["string", "number"],
|
|
@@ -35417,7 +35672,7 @@ var require_token_io = __commonJS((exports, module) => {
|
|
|
35417
35672
|
module.exports = __toCommonJS(token_io_exports);
|
|
35418
35673
|
var import_path = __toESM2(__require("path"));
|
|
35419
35674
|
var import_fs = __toESM2(__require("fs"));
|
|
35420
|
-
var
|
|
35675
|
+
var import_os = __toESM2(__require("os"));
|
|
35421
35676
|
var import_token_error = require_token_error();
|
|
35422
35677
|
function findRootDir() {
|
|
35423
35678
|
try {
|
|
@@ -35438,11 +35693,11 @@ var require_token_io = __commonJS((exports, module) => {
|
|
|
35438
35693
|
if (process.env.XDG_DATA_HOME) {
|
|
35439
35694
|
return process.env.XDG_DATA_HOME;
|
|
35440
35695
|
}
|
|
35441
|
-
switch (
|
|
35696
|
+
switch (import_os.default.platform()) {
|
|
35442
35697
|
case "darwin":
|
|
35443
|
-
return import_path.default.join(
|
|
35698
|
+
return import_path.default.join(import_os.default.homedir(), "Library/Application Support");
|
|
35444
35699
|
case "linux":
|
|
35445
|
-
return import_path.default.join(
|
|
35700
|
+
return import_path.default.join(import_os.default.homedir(), ".local/share");
|
|
35446
35701
|
case "win32":
|
|
35447
35702
|
if (process.env.LOCALAPPDATA) {
|
|
35448
35703
|
return process.env.LOCALAPPDATA;
|
|
@@ -35552,10 +35807,10 @@ var require_oauth = __commonJS((exports, module) => {
|
|
|
35552
35807
|
refreshTokenRequest: () => refreshTokenRequest
|
|
35553
35808
|
});
|
|
35554
35809
|
module.exports = __toCommonJS(oauth_exports);
|
|
35555
|
-
var
|
|
35810
|
+
var import_os = __require("os");
|
|
35556
35811
|
var VERCEL_ISSUER = "https://vercel.com";
|
|
35557
35812
|
var VERCEL_CLI_CLIENT_ID = "cl_HYyOPBNtFMfHhaUn9L4QPfTZz6TP47bp";
|
|
35558
|
-
var userAgent = `@vercel/oidc node-${process.version} ${(0,
|
|
35813
|
+
var userAgent = `@vercel/oidc node-${process.version} ${(0, import_os.platform)()} (${(0, import_os.arch)()}) ${(0, import_os.hostname)()}`;
|
|
35559
35814
|
var _tokenEndpoint = null;
|
|
35560
35815
|
async function getTokenEndpoint() {
|
|
35561
35816
|
if (_tokenEndpoint) {
|
|
@@ -40695,8 +40950,8 @@ function prepareCallSettings({
|
|
|
40695
40950
|
seed
|
|
40696
40951
|
};
|
|
40697
40952
|
}
|
|
40698
|
-
function isNonEmptyObject(
|
|
40699
|
-
return
|
|
40953
|
+
function isNonEmptyObject(object22) {
|
|
40954
|
+
return object22 != null && Object.keys(object22).length > 0;
|
|
40700
40955
|
}
|
|
40701
40956
|
async function prepareToolsAndToolChoice({
|
|
40702
40957
|
tools,
|
|
@@ -47784,7 +48039,7 @@ var import_api2, import_api3, __defProp2, __export2 = (target, all) => {
|
|
|
47784
48039
|
createElementStreamTransform() {
|
|
47785
48040
|
return;
|
|
47786
48041
|
}
|
|
47787
|
-
}),
|
|
48042
|
+
}), object3 = ({
|
|
47788
48043
|
schema: inputSchema,
|
|
47789
48044
|
name: name232,
|
|
47790
48045
|
description
|
|
@@ -52522,7 +52777,7 @@ var init_dist8 = __esm(() => {
|
|
|
52522
52777
|
array: () => array2,
|
|
52523
52778
|
choice: () => choice,
|
|
52524
52779
|
json: () => json3,
|
|
52525
|
-
object: () =>
|
|
52780
|
+
object: () => object3,
|
|
52526
52781
|
text: () => text
|
|
52527
52782
|
});
|
|
52528
52783
|
originalGenerateId = createIdGenerator({
|
|
@@ -54008,7 +54263,7 @@ function buildMemoryListConditions(filter) {
|
|
|
54008
54263
|
conditions.push("status = 'active'");
|
|
54009
54264
|
}
|
|
54010
54265
|
if (filter.project_id) {
|
|
54011
|
-
conditions.push("project_id = ?");
|
|
54266
|
+
conditions.push(filter.include_unassigned_project === true ? "(project_id = ? OR project_id IS NULL)" : "project_id = ?");
|
|
54012
54267
|
params.push(filter.project_id);
|
|
54013
54268
|
}
|
|
54014
54269
|
if (filter.agent_id) {
|
|
@@ -54088,6 +54343,7 @@ function listMemoriesPage(filter, db) {
|
|
|
54088
54343
|
pinned: f.pinned,
|
|
54089
54344
|
agent_id: f.agent_id,
|
|
54090
54345
|
project_id: f.project_id,
|
|
54346
|
+
include_unassigned_project: f.project_id && f.include_unassigned_project === true ? true : undefined,
|
|
54091
54347
|
session_id: f.session_id,
|
|
54092
54348
|
machine_id: f.machine_id,
|
|
54093
54349
|
visible_to_machine_id: f.visible_to_machine_id,
|
|
@@ -54112,7 +54368,7 @@ function listMemoriesPage(filter, db) {
|
|
|
54112
54368
|
if (conditions.length > 0) {
|
|
54113
54369
|
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
54114
54370
|
}
|
|
54115
|
-
sql += " ORDER BY importance DESC, created_at DESC";
|
|
54371
|
+
sql += " ORDER BY importance DESC, created_at DESC, id DESC";
|
|
54116
54372
|
if (f.limit) {
|
|
54117
54373
|
sql += " LIMIT ?";
|
|
54118
54374
|
params.push(f.limit);
|
|
@@ -55435,17 +55691,83 @@ function updateAgent(id, updates, db) {
|
|
|
55435
55691
|
// src/db/locks.ts
|
|
55436
55692
|
init_database();
|
|
55437
55693
|
init_api_mode();
|
|
55438
|
-
|
|
55694
|
+
|
|
55695
|
+
// src/db/api-response-contract.ts
|
|
55696
|
+
class MementosApiProtocolError extends Error {
|
|
55697
|
+
code = "MEMENTOS_API_PROTOCOL";
|
|
55698
|
+
constructor(operation, detail) {
|
|
55699
|
+
super(`mementos cloud ${operation} returned a malformed 2xx response: ${detail}`);
|
|
55700
|
+
this.name = "MementosApiProtocolError";
|
|
55701
|
+
}
|
|
55702
|
+
}
|
|
55703
|
+
function expectObject(value, operation) {
|
|
55704
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
55705
|
+
throw new MementosApiProtocolError(operation, "expected a JSON object");
|
|
55706
|
+
}
|
|
55707
|
+
return value;
|
|
55708
|
+
}
|
|
55709
|
+
function expectArray(value, operation, field) {
|
|
55710
|
+
if (!Array.isArray(value)) {
|
|
55711
|
+
throw new MementosApiProtocolError(operation, field ? `expected '${field}' to be an array` : "expected a JSON array");
|
|
55712
|
+
}
|
|
55713
|
+
return value;
|
|
55714
|
+
}
|
|
55715
|
+
function expectString(object, field, operation, options = {}) {
|
|
55716
|
+
const value = object[field];
|
|
55717
|
+
if (typeof value !== "string" || !options.allowEmpty && value.length === 0) {
|
|
55718
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be ${options.allowEmpty ? "a string" : "a non-empty string"}`);
|
|
55719
|
+
}
|
|
55720
|
+
return value;
|
|
55721
|
+
}
|
|
55722
|
+
function expectBoolean(object, field, operation) {
|
|
55723
|
+
const value = object[field];
|
|
55724
|
+
if (typeof value !== "boolean") {
|
|
55725
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a boolean`);
|
|
55726
|
+
}
|
|
55727
|
+
return value;
|
|
55728
|
+
}
|
|
55729
|
+
function expectNonNegativeInteger(object, field, operation) {
|
|
55730
|
+
const value = object[field];
|
|
55731
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
55732
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a non-negative safe integer`);
|
|
55733
|
+
}
|
|
55734
|
+
return value;
|
|
55735
|
+
}
|
|
55736
|
+
|
|
55737
|
+
// src/db/locks.ts
|
|
55738
|
+
var RESOURCE_TYPES = new Set(["project", "memory", "entity", "agent", "connector", "file"]);
|
|
55739
|
+
var LOCK_TYPES = new Set(["advisory", "exclusive"]);
|
|
55740
|
+
function parseLockRow(row, operation = "lock response") {
|
|
55741
|
+
const object = expectObject(row, operation);
|
|
55742
|
+
const resourceType = expectString(object, "resource_type", operation);
|
|
55743
|
+
const lockType = expectString(object, "lock_type", operation);
|
|
55744
|
+
if (!RESOURCE_TYPES.has(resourceType)) {
|
|
55745
|
+
throw new MementosApiProtocolError(operation, "unsupported 'resource_type'");
|
|
55746
|
+
}
|
|
55747
|
+
if (!LOCK_TYPES.has(lockType)) {
|
|
55748
|
+
throw new MementosApiProtocolError(operation, "unsupported 'lock_type'");
|
|
55749
|
+
}
|
|
55750
|
+
const timestamp = (field) => {
|
|
55751
|
+
const value = object[field];
|
|
55752
|
+
const date = value instanceof Date ? value : new Date(typeof value === "string" ? value : Number.NaN);
|
|
55753
|
+
if (!Number.isFinite(date.getTime())) {
|
|
55754
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a timestamp`);
|
|
55755
|
+
}
|
|
55756
|
+
return date.toISOString();
|
|
55757
|
+
};
|
|
55439
55758
|
return {
|
|
55440
|
-
id:
|
|
55441
|
-
resource_type:
|
|
55442
|
-
resource_id:
|
|
55443
|
-
agent_id:
|
|
55444
|
-
lock_type:
|
|
55445
|
-
locked_at:
|
|
55446
|
-
expires_at:
|
|
55759
|
+
id: expectString(object, "id", operation),
|
|
55760
|
+
resource_type: resourceType,
|
|
55761
|
+
resource_id: expectString(object, "resource_id", operation),
|
|
55762
|
+
agent_id: expectString(object, "agent_id", operation),
|
|
55763
|
+
lock_type: lockType,
|
|
55764
|
+
locked_at: timestamp("locked_at"),
|
|
55765
|
+
expires_at: timestamp("expires_at")
|
|
55447
55766
|
};
|
|
55448
55767
|
}
|
|
55768
|
+
function parseLockList(value, operation) {
|
|
55769
|
+
return expectArray(value, operation).map((row, index) => parseLockRow(row, `${operation} item ${index}`));
|
|
55770
|
+
}
|
|
55449
55771
|
function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive", ttlSeconds = 300, db) {
|
|
55450
55772
|
if (!db && isApiMode()) {
|
|
55451
55773
|
try {
|
|
@@ -55456,7 +55778,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
55456
55778
|
lock_type: lockType,
|
|
55457
55779
|
ttl_seconds: ttlSeconds
|
|
55458
55780
|
});
|
|
55459
|
-
return data;
|
|
55781
|
+
return parseLockRow(data, "POST /locks");
|
|
55460
55782
|
} catch (e) {
|
|
55461
55783
|
if (e instanceof ApiRequestError && e.status === 409)
|
|
55462
55784
|
return null;
|
|
@@ -55465,7 +55787,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
55465
55787
|
}
|
|
55466
55788
|
const d = db || getDatabase();
|
|
55467
55789
|
cleanExpiredLocks(d);
|
|
55468
|
-
const ownLock = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND agent_id = ? AND lock_type = ? AND expires_at >
|
|
55790
|
+
const ownLock = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND agent_id = ? AND lock_type = ? AND expires_at > ?").get(resourceType, resourceId, agentId, lockType, now());
|
|
55469
55791
|
if (ownLock) {
|
|
55470
55792
|
const newExpiry = new Date(Date.now() + ttlSeconds * 1000).toISOString();
|
|
55471
55793
|
d.run("UPDATE resource_locks SET expires_at = ? WHERE id = ?", [
|
|
@@ -55475,7 +55797,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
55475
55797
|
return parseLockRow({ ...ownLock, expires_at: newExpiry });
|
|
55476
55798
|
}
|
|
55477
55799
|
if (lockType === "exclusive") {
|
|
55478
|
-
const existing = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = 'exclusive' AND agent_id != ? AND expires_at >
|
|
55800
|
+
const existing = d.query("SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = 'exclusive' AND agent_id != ? AND expires_at > ?").get(resourceType, resourceId, agentId, now());
|
|
55479
55801
|
if (existing) {
|
|
55480
55802
|
return null;
|
|
55481
55803
|
}
|
|
@@ -55496,8 +55818,11 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
|
|
|
55496
55818
|
}
|
|
55497
55819
|
function releaseLock(lockId, agentId, db) {
|
|
55498
55820
|
if (!db && isApiMode()) {
|
|
55499
|
-
const
|
|
55500
|
-
|
|
55821
|
+
const operation = `DELETE /locks/${encodeURIComponent(lockId)}`;
|
|
55822
|
+
const { status, data } = apiJson("DELETE", `/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId }, { allow404: true });
|
|
55823
|
+
if (status === 404)
|
|
55824
|
+
return false;
|
|
55825
|
+
return expectBoolean(expectObject(data, operation), "released", operation);
|
|
55501
55826
|
}
|
|
55502
55827
|
const d = db || getDatabase();
|
|
55503
55828
|
const result = d.run("DELETE FROM resource_locks WHERE id = ? AND agent_id = ?", [lockId, agentId]);
|
|
@@ -55510,8 +55835,9 @@ function releaseResourceLocks(agentId, resourceType, resourceId, db) {
|
|
|
55510
55835
|
}
|
|
55511
55836
|
function releaseAllAgentLocks(agentId, db) {
|
|
55512
55837
|
if (!db && isApiMode()) {
|
|
55838
|
+
const operation = `DELETE /agents/${encodeURIComponent(agentId)}/locks`;
|
|
55513
55839
|
const { data } = apiJson("DELETE", `/agents/${encodeURIComponent(agentId)}/locks`);
|
|
55514
|
-
return data
|
|
55840
|
+
return expectNonNegativeInteger(expectObject(data, operation), "released", operation);
|
|
55515
55841
|
}
|
|
55516
55842
|
const d = db || getDatabase();
|
|
55517
55843
|
const result = d.run("DELETE FROM resource_locks WHERE agent_id = ?", [agentId]);
|
|
@@ -55521,69 +55847,714 @@ function checkLock(resourceType, resourceId, lockType, db) {
|
|
|
55521
55847
|
if (!db && isApiMode()) {
|
|
55522
55848
|
const q = toQuery({ resource_type: resourceType, resource_id: resourceId, lock_type: lockType });
|
|
55523
55849
|
const { data } = apiJson("GET", `/locks${q}`);
|
|
55524
|
-
return data
|
|
55850
|
+
return parseLockList(data, "GET /locks");
|
|
55525
55851
|
}
|
|
55526
55852
|
const d = db || getDatabase();
|
|
55527
55853
|
cleanExpiredLocks(d);
|
|
55528
|
-
const query = lockType ? "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at >
|
|
55529
|
-
const rows = lockType ? d.query(query).all(resourceType, resourceId, lockType) : d.query(query).all(resourceType, resourceId);
|
|
55530
|
-
return rows.map(parseLockRow);
|
|
55854
|
+
const query = lockType ? "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at > ?" : "SELECT * FROM resource_locks WHERE resource_type = ? AND resource_id = ? AND expires_at > ?";
|
|
55855
|
+
const rows = lockType ? d.query(query).all(resourceType, resourceId, lockType, now()) : d.query(query).all(resourceType, resourceId, now());
|
|
55856
|
+
return rows.map((row) => parseLockRow(row));
|
|
55531
55857
|
}
|
|
55532
55858
|
function agentHoldsLock(agentId, resourceType, resourceId, lockType, db) {
|
|
55859
|
+
if (!db && isApiMode()) {
|
|
55860
|
+
const locks = checkLock(resourceType, resourceId, lockType, db);
|
|
55861
|
+
return locks.find((l) => l.agent_id === agentId) ?? null;
|
|
55862
|
+
}
|
|
55533
55863
|
const d = db || getDatabase();
|
|
55534
|
-
const query = lockType ? "SELECT * FROM resource_locks WHERE agent_id = ? AND resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at >
|
|
55535
|
-
const row = lockType ? d.query(query).get(agentId, resourceType, resourceId, lockType) : d.query(query).get(agentId, resourceType, resourceId);
|
|
55864
|
+
const query = lockType ? "SELECT * FROM resource_locks WHERE agent_id = ? AND resource_type = ? AND resource_id = ? AND lock_type = ? AND expires_at > ?" : "SELECT * FROM resource_locks WHERE agent_id = ? AND resource_type = ? AND resource_id = ? AND expires_at > ?";
|
|
55865
|
+
const row = lockType ? d.query(query).get(agentId, resourceType, resourceId, lockType, now()) : d.query(query).get(agentId, resourceType, resourceId, now());
|
|
55536
55866
|
return row ? parseLockRow(row) : null;
|
|
55537
55867
|
}
|
|
55538
55868
|
function listAgentLocks(agentId, db) {
|
|
55539
55869
|
if (!db && isApiMode()) {
|
|
55870
|
+
const operation = `GET /agents/${encodeURIComponent(agentId)}/locks`;
|
|
55540
55871
|
const { data } = apiJson("GET", `/agents/${encodeURIComponent(agentId)}/locks`);
|
|
55541
|
-
return data
|
|
55872
|
+
return parseLockList(data, operation);
|
|
55542
55873
|
}
|
|
55543
55874
|
const d = db || getDatabase();
|
|
55544
55875
|
cleanExpiredLocks(d);
|
|
55545
|
-
const rows = d.query("SELECT * FROM resource_locks WHERE agent_id = ? AND expires_at >
|
|
55546
|
-
return rows.map(parseLockRow);
|
|
55876
|
+
const rows = d.query("SELECT * FROM resource_locks WHERE agent_id = ? AND expires_at > ? ORDER BY locked_at DESC").all(agentId, now());
|
|
55877
|
+
return rows.map((row) => parseLockRow(row));
|
|
55547
55878
|
}
|
|
55548
55879
|
function cleanExpiredLocks(db) {
|
|
55549
55880
|
if (!db && isApiMode()) {
|
|
55881
|
+
const operation = "POST /locks/clean";
|
|
55550
55882
|
const { data } = apiJson("POST", "/locks/clean");
|
|
55551
|
-
return data
|
|
55883
|
+
return expectNonNegativeInteger(expectObject(data, operation), "cleaned", operation);
|
|
55552
55884
|
}
|
|
55553
55885
|
const d = db || getDatabase();
|
|
55554
|
-
const result = d.run("DELETE FROM resource_locks WHERE expires_at <=
|
|
55886
|
+
const result = d.run("DELETE FROM resource_locks WHERE expires_at <= ?", now());
|
|
55555
55887
|
return result.changes;
|
|
55556
55888
|
}
|
|
55557
|
-
// src/
|
|
55889
|
+
// src/db/audit.ts
|
|
55890
|
+
init_storage();
|
|
55558
55891
|
init_database();
|
|
55892
|
+
init_api_mode();
|
|
55893
|
+
import { createHash as createHash2, createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
55894
|
+
|
|
55895
|
+
// src/audit-contract.ts
|
|
55896
|
+
var AUDIT_TRAIL_CONTRACT = "mementos.audit.trail.v1";
|
|
55897
|
+
var AUDIT_EXPORT_CONTRACT = "mementos.audit.export.v1";
|
|
55898
|
+
var AUDIT_STATS_CONTRACT = "mementos.audit.stats.v1";
|
|
55899
|
+
var AUDIT_CURSOR_VERSION = 1;
|
|
55900
|
+
var AUDIT_DEFAULT_LIMIT = 50;
|
|
55901
|
+
var AUDIT_OPERATIONS = [
|
|
55902
|
+
"create",
|
|
55903
|
+
"update",
|
|
55904
|
+
"delete",
|
|
55905
|
+
"archive",
|
|
55906
|
+
"restore",
|
|
55907
|
+
"read"
|
|
55908
|
+
];
|
|
55909
|
+
|
|
55910
|
+
class AuditContractError extends Error {
|
|
55911
|
+
code = "MEMENTOS_AUDIT_CONTRACT";
|
|
55912
|
+
constructor(message) {
|
|
55913
|
+
super(message);
|
|
55914
|
+
this.name = "AuditContractError";
|
|
55915
|
+
}
|
|
55916
|
+
}
|
|
55917
|
+
var ISO_UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
55918
|
+
var MD5_HEX = /^[0-9a-f]{32}$/;
|
|
55919
|
+
var ENTRY_KEYS = new Set([
|
|
55920
|
+
"id",
|
|
55921
|
+
"memory_id",
|
|
55922
|
+
"memory_key",
|
|
55923
|
+
"operation",
|
|
55924
|
+
"agent_id",
|
|
55925
|
+
"old_value_hash",
|
|
55926
|
+
"new_value_hash",
|
|
55927
|
+
"changes",
|
|
55928
|
+
"created_at"
|
|
55929
|
+
]);
|
|
55930
|
+
var PAGE_KEYS = new Set([
|
|
55931
|
+
"contract",
|
|
55932
|
+
"entries",
|
|
55933
|
+
"count",
|
|
55934
|
+
"total",
|
|
55935
|
+
"limit",
|
|
55936
|
+
"cursor",
|
|
55937
|
+
"next_cursor",
|
|
55938
|
+
"consumed",
|
|
55939
|
+
"has_more",
|
|
55940
|
+
"complete",
|
|
55941
|
+
"snapshot_at",
|
|
55942
|
+
"filters",
|
|
55943
|
+
"sort"
|
|
55944
|
+
]);
|
|
55945
|
+
function object(value, label) {
|
|
55946
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
55947
|
+
throw new AuditContractError(`${label} must be a JSON object`);
|
|
55948
|
+
}
|
|
55949
|
+
return value;
|
|
55950
|
+
}
|
|
55951
|
+
function exactKeys(value, allowed, label) {
|
|
55952
|
+
const unexpected = Object.keys(value).filter((key) => !allowed.has(key));
|
|
55953
|
+
if (unexpected.length)
|
|
55954
|
+
throw new AuditContractError(`${label} has unexpected field '${unexpected[0]}'`);
|
|
55955
|
+
}
|
|
55956
|
+
function string(value, label, options = {}) {
|
|
55957
|
+
if (options.nullable && value === null)
|
|
55958
|
+
return null;
|
|
55959
|
+
if (typeof value !== "string" || value.length === 0 || value.length > (options.max ?? 2048) || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
55960
|
+
throw new AuditContractError(`${label} must be a non-empty printable string of at most ${options.max ?? 2048} characters`);
|
|
55961
|
+
}
|
|
55962
|
+
return value;
|
|
55963
|
+
}
|
|
55964
|
+
function cursorString(value, label) {
|
|
55965
|
+
if (value === null)
|
|
55966
|
+
return null;
|
|
55967
|
+
if (typeof value !== "string" || !value || value.length > 4096 || !/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
55968
|
+
throw new AuditContractError(`${label} must be a bounded base64url cursor or null`);
|
|
55969
|
+
}
|
|
55970
|
+
return value;
|
|
55971
|
+
}
|
|
55972
|
+
function integer(value, label, minimum = 0) {
|
|
55973
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
55974
|
+
throw new AuditContractError(`${label} must be a safe integer >= ${minimum}`);
|
|
55975
|
+
}
|
|
55976
|
+
return value;
|
|
55977
|
+
}
|
|
55978
|
+
function boolean(value, label) {
|
|
55979
|
+
if (typeof value !== "boolean")
|
|
55980
|
+
throw new AuditContractError(`${label} must be a boolean`);
|
|
55981
|
+
return value;
|
|
55982
|
+
}
|
|
55983
|
+
function canonicalAuditTimestamp(value, label) {
|
|
55984
|
+
if (value instanceof Date)
|
|
55985
|
+
value = value.toISOString();
|
|
55986
|
+
if (typeof value !== "string")
|
|
55987
|
+
throw new AuditContractError(`${label} must be a canonical UTC timestamp`);
|
|
55988
|
+
const candidate = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,3})?$/.test(value) ? `${value.replace(" ", "T")}${value.includes(".") ? "" : ".000"}Z` : value;
|
|
55989
|
+
if (!ISO_UTC.test(candidate))
|
|
55990
|
+
throw new AuditContractError(`${label} must use YYYY-MM-DDTHH:mm:ss.sssZ`);
|
|
55991
|
+
const parsed = new Date(candidate);
|
|
55992
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== candidate) {
|
|
55993
|
+
throw new AuditContractError(`${label} must be a real calendar timestamp`);
|
|
55994
|
+
}
|
|
55995
|
+
return candidate;
|
|
55996
|
+
}
|
|
55997
|
+
function auditOperation(value, label = "operation") {
|
|
55998
|
+
if (typeof value !== "string" || !AUDIT_OPERATIONS.includes(value)) {
|
|
55999
|
+
throw new AuditContractError(`${label} must be one of: ${AUDIT_OPERATIONS.join(", ")}`);
|
|
56000
|
+
}
|
|
56001
|
+
return value;
|
|
56002
|
+
}
|
|
56003
|
+
function nullableHash(value, label) {
|
|
56004
|
+
if (value === null)
|
|
56005
|
+
return null;
|
|
56006
|
+
if (typeof value !== "string" || !MD5_HEX.test(value)) {
|
|
56007
|
+
throw new AuditContractError(`${label} must be a lowercase md5 hex digest or null`);
|
|
56008
|
+
}
|
|
56009
|
+
return value;
|
|
56010
|
+
}
|
|
56011
|
+
function record(value, label) {
|
|
56012
|
+
return object(value, label);
|
|
56013
|
+
}
|
|
56014
|
+
function validateAuditEntry(value, label = "audit entry") {
|
|
56015
|
+
const row = object(value, label);
|
|
56016
|
+
exactKeys(row, ENTRY_KEYS, label);
|
|
56017
|
+
return {
|
|
56018
|
+
id: string(row.id, `${label}.id`, { max: 512 }),
|
|
56019
|
+
memory_id: string(row.memory_id, `${label}.memory_id`, { max: 512 }),
|
|
56020
|
+
memory_key: string(row.memory_key, `${label}.memory_key`, { nullable: true, max: 4096 }),
|
|
56021
|
+
operation: auditOperation(row.operation, `${label}.operation`),
|
|
56022
|
+
agent_id: string(row.agent_id, `${label}.agent_id`, { nullable: true, max: 512 }),
|
|
56023
|
+
old_value_hash: nullableHash(row.old_value_hash, `${label}.old_value_hash`),
|
|
56024
|
+
new_value_hash: nullableHash(row.new_value_hash, `${label}.new_value_hash`),
|
|
56025
|
+
changes: record(row.changes, `${label}.changes`),
|
|
56026
|
+
created_at: canonicalAuditTimestamp(row.created_at, `${label}.created_at`)
|
|
56027
|
+
};
|
|
56028
|
+
}
|
|
56029
|
+
function nullableTimestamp2(value, label) {
|
|
56030
|
+
return value === null ? null : canonicalAuditTimestamp(value, label);
|
|
56031
|
+
}
|
|
56032
|
+
function validateFilters(value) {
|
|
56033
|
+
const filters = object(value, "audit page filters");
|
|
56034
|
+
const expected = new Set(["memory_id", "since", "until", "operation", "agent_id"]);
|
|
56035
|
+
exactKeys(filters, expected, "audit page filters");
|
|
56036
|
+
const result = {
|
|
56037
|
+
memory_id: string(filters.memory_id, "filters.memory_id", { nullable: true, max: 512 }),
|
|
56038
|
+
since: nullableTimestamp2(filters.since, "filters.since"),
|
|
56039
|
+
until: nullableTimestamp2(filters.until, "filters.until"),
|
|
56040
|
+
operation: filters.operation === null ? null : auditOperation(filters.operation, "filters.operation"),
|
|
56041
|
+
agent_id: string(filters.agent_id, "filters.agent_id", { nullable: true, max: 512 })
|
|
56042
|
+
};
|
|
56043
|
+
if (result.since && result.until && result.since > result.until) {
|
|
56044
|
+
throw new AuditContractError("filters.since must not be after filters.until");
|
|
56045
|
+
}
|
|
56046
|
+
return result;
|
|
56047
|
+
}
|
|
56048
|
+
function compareEntryOrder(left, right) {
|
|
56049
|
+
if (left.created_at !== right.created_at)
|
|
56050
|
+
return left.created_at > right.created_at ? -1 : 1;
|
|
56051
|
+
return left.id > right.id ? -1 : left.id < right.id ? 1 : 0;
|
|
56052
|
+
}
|
|
56053
|
+
function validateAuditPage(value, expected) {
|
|
56054
|
+
const page = object(value, "audit page");
|
|
56055
|
+
exactKeys(page, PAGE_KEYS, "audit page");
|
|
56056
|
+
if (page.contract !== expected.contract)
|
|
56057
|
+
throw new AuditContractError(`expected contract '${expected.contract}'`);
|
|
56058
|
+
const entries = Array.isArray(page.entries) ? page.entries.map((entry, index) => validateAuditEntry(entry, `entries[${index}]`)) : (() => {
|
|
56059
|
+
throw new AuditContractError("entries must be an array");
|
|
56060
|
+
})();
|
|
56061
|
+
const count = integer(page.count, "count");
|
|
56062
|
+
const total = integer(page.total, "total");
|
|
56063
|
+
const limit = integer(page.limit, "limit", 1);
|
|
56064
|
+
const requestedLimit = integer(expected.limit, "requested limit", 1);
|
|
56065
|
+
if (limit !== requestedLimit)
|
|
56066
|
+
throw new AuditContractError("limit receipt does not match the request");
|
|
56067
|
+
const consumed = integer(page.consumed, "consumed");
|
|
56068
|
+
const cursor = cursorString(page.cursor, "cursor");
|
|
56069
|
+
const nextCursor = cursorString(page.next_cursor, "next_cursor");
|
|
56070
|
+
const hasMore = boolean(page.has_more, "has_more");
|
|
56071
|
+
const complete = boolean(page.complete, "complete");
|
|
56072
|
+
const snapshotAt = canonicalAuditTimestamp(page.snapshot_at, "snapshot_at");
|
|
56073
|
+
const filters = validateFilters(page.filters);
|
|
56074
|
+
const sort = object(page.sort, "sort");
|
|
56075
|
+
exactKeys(sort, new Set(["field", "direction", "tie_breaker"]), "sort");
|
|
56076
|
+
if (sort.field !== "created_at" || sort.direction !== "desc" || sort.tie_breaker !== "id") {
|
|
56077
|
+
throw new AuditContractError("sort must be created_at desc with id tie-breaker");
|
|
56078
|
+
}
|
|
56079
|
+
if (cursor !== expected.cursor)
|
|
56080
|
+
throw new AuditContractError("cursor receipt does not match the request");
|
|
56081
|
+
for (const key of ["memory_id", "since", "until", "operation", "agent_id"]) {
|
|
56082
|
+
if (filters[key] !== expected.filters[key]) {
|
|
56083
|
+
throw new AuditContractError(`filters.${key} receipt does not match the request`);
|
|
56084
|
+
}
|
|
56085
|
+
}
|
|
56086
|
+
if (count !== entries.length || count > limit || total < count || consumed < count || consumed > total) {
|
|
56087
|
+
throw new AuditContractError("count/limit/consumed/total fields are inconsistent");
|
|
56088
|
+
}
|
|
56089
|
+
if (hasMore !== consumed < total)
|
|
56090
|
+
throw new AuditContractError("has_more does not match consumed/total");
|
|
56091
|
+
if (hasMore !== (nextCursor !== null))
|
|
56092
|
+
throw new AuditContractError("next_cursor does not match has_more");
|
|
56093
|
+
if (total > 0 && count === 0)
|
|
56094
|
+
throw new AuditContractError("non-empty audit result must make progress on every page");
|
|
56095
|
+
if (hasMore && nextCursor === cursor)
|
|
56096
|
+
throw new AuditContractError("next_cursor must advance beyond the request cursor");
|
|
56097
|
+
const shouldBeComplete = cursor === null && !hasMore && consumed === total;
|
|
56098
|
+
if (complete !== shouldBeComplete)
|
|
56099
|
+
throw new AuditContractError("complete is not truthful for this page");
|
|
56100
|
+
if (cursor === null && consumed !== count)
|
|
56101
|
+
throw new AuditContractError("initial page consumed must equal count");
|
|
56102
|
+
for (let index = 1;index < entries.length; index++) {
|
|
56103
|
+
if (compareEntryOrder(entries[index - 1], entries[index]) >= 0) {
|
|
56104
|
+
throw new AuditContractError("entries are not strictly ordered by created_at desc, id desc");
|
|
56105
|
+
}
|
|
56106
|
+
}
|
|
56107
|
+
const ids = new Set;
|
|
56108
|
+
for (const entry of entries) {
|
|
56109
|
+
if (ids.has(entry.id))
|
|
56110
|
+
throw new AuditContractError(`duplicate audit entry id '${entry.id}'`);
|
|
56111
|
+
ids.add(entry.id);
|
|
56112
|
+
if (filters.memory_id && entry.memory_id !== filters.memory_id)
|
|
56113
|
+
throw new AuditContractError("entry violates memory_id filter");
|
|
56114
|
+
if (filters.operation && entry.operation !== filters.operation)
|
|
56115
|
+
throw new AuditContractError("entry violates operation filter");
|
|
56116
|
+
if (filters.agent_id && entry.agent_id !== filters.agent_id)
|
|
56117
|
+
throw new AuditContractError("entry violates agent_id filter");
|
|
56118
|
+
if (filters.since && entry.created_at < filters.since)
|
|
56119
|
+
throw new AuditContractError("entry violates since filter");
|
|
56120
|
+
if (filters.until && entry.created_at > filters.until)
|
|
56121
|
+
throw new AuditContractError("entry violates until filter");
|
|
56122
|
+
if (entry.created_at > snapshotAt)
|
|
56123
|
+
throw new AuditContractError("entry is newer than the snapshot boundary");
|
|
56124
|
+
}
|
|
56125
|
+
return {
|
|
56126
|
+
contract: expected.contract,
|
|
56127
|
+
entries,
|
|
56128
|
+
count,
|
|
56129
|
+
total,
|
|
56130
|
+
limit,
|
|
56131
|
+
cursor,
|
|
56132
|
+
next_cursor: nextCursor,
|
|
56133
|
+
consumed,
|
|
56134
|
+
has_more: hasMore,
|
|
56135
|
+
complete,
|
|
56136
|
+
snapshot_at: snapshotAt,
|
|
56137
|
+
filters,
|
|
56138
|
+
sort: { field: "created_at", direction: "desc", tie_breaker: "id" }
|
|
56139
|
+
};
|
|
56140
|
+
}
|
|
56141
|
+
function validateAuditStats(value) {
|
|
56142
|
+
const stats = object(value, "audit stats");
|
|
56143
|
+
exactKeys(stats, new Set(["contract", "total_entries", "by_operation", "recent_24h", "snapshot_at"]), "audit stats");
|
|
56144
|
+
if (stats.contract !== AUDIT_STATS_CONTRACT)
|
|
56145
|
+
throw new AuditContractError(`expected contract '${AUDIT_STATS_CONTRACT}'`);
|
|
56146
|
+
const total = integer(stats.total_entries, "total_entries");
|
|
56147
|
+
const recent = integer(stats.recent_24h, "recent_24h");
|
|
56148
|
+
const byOperationObject = object(stats.by_operation, "by_operation");
|
|
56149
|
+
exactKeys(byOperationObject, new Set(AUDIT_OPERATIONS), "by_operation");
|
|
56150
|
+
const byOperation = Object.fromEntries(AUDIT_OPERATIONS.map((operation) => [operation, integer(byOperationObject[operation], `by_operation.${operation}`)]));
|
|
56151
|
+
if (Object.values(byOperation).reduce((sum, count) => sum + count, 0) !== total) {
|
|
56152
|
+
throw new AuditContractError("by_operation counts do not sum to total_entries");
|
|
56153
|
+
}
|
|
56154
|
+
if (recent > total)
|
|
56155
|
+
throw new AuditContractError("recent_24h exceeds total_entries");
|
|
56156
|
+
return {
|
|
56157
|
+
contract: AUDIT_STATS_CONTRACT,
|
|
56158
|
+
total_entries: total,
|
|
56159
|
+
by_operation: byOperation,
|
|
56160
|
+
recent_24h: recent,
|
|
56161
|
+
snapshot_at: canonicalAuditTimestamp(stats.snapshot_at, "snapshot_at")
|
|
56162
|
+
};
|
|
56163
|
+
}
|
|
56164
|
+
|
|
56165
|
+
// src/db/audit.ts
|
|
56166
|
+
var DEFAULT_TRAIL_LIMIT = AUDIT_DEFAULT_LIMIT;
|
|
56167
|
+
var DEFAULT_EXPORT_LIMIT = AUDIT_DEFAULT_LIMIT;
|
|
56168
|
+
var MAX_AUDIT_LIMIT = 1000;
|
|
56169
|
+
var MAX_CURSOR_BYTES = 4096;
|
|
56170
|
+
var PROCESS_CURSOR_SECRET = randomBytes(32).toString("base64url");
|
|
56171
|
+
var CURSOR_MAC_DOMAIN = "mementos.audit.cursor.v1\x00";
|
|
56172
|
+
function protocol(operation, error) {
|
|
56173
|
+
if (error instanceof MementosApiProtocolError)
|
|
56174
|
+
throw error;
|
|
56175
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
56176
|
+
throw new MementosApiProtocolError(operation, detail);
|
|
56177
|
+
}
|
|
56178
|
+
function safeLimit(value, fallback) {
|
|
56179
|
+
const resolved = value ?? fallback;
|
|
56180
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > MAX_AUDIT_LIMIT) {
|
|
56181
|
+
throw new AuditContractError(`limit must be an integer between 1 and ${MAX_AUDIT_LIMIT}`);
|
|
56182
|
+
}
|
|
56183
|
+
return resolved;
|
|
56184
|
+
}
|
|
56185
|
+
function printableIdentifier(value, label, maximum = 512) {
|
|
56186
|
+
if (value === undefined)
|
|
56187
|
+
return null;
|
|
56188
|
+
if (!value || value.length > maximum || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
56189
|
+
throw new AuditContractError(`${label} must be a non-empty printable string of at most ${maximum} characters`);
|
|
56190
|
+
}
|
|
56191
|
+
return value;
|
|
56192
|
+
}
|
|
56193
|
+
function normalizeAuditFilters(input) {
|
|
56194
|
+
const filters = {
|
|
56195
|
+
memory_id: printableIdentifier(input.memory_id, "memory_id"),
|
|
56196
|
+
since: input.since === undefined ? null : canonicalAuditTimestamp(input.since, "since"),
|
|
56197
|
+
until: input.until === undefined ? null : canonicalAuditTimestamp(input.until, "until"),
|
|
56198
|
+
operation: input.operation === undefined ? null : auditOperation(input.operation),
|
|
56199
|
+
agent_id: printableIdentifier(input.agent_id, "agent_id")
|
|
56200
|
+
};
|
|
56201
|
+
if (filters.since && filters.until && filters.since > filters.until) {
|
|
56202
|
+
throw new AuditContractError("since must not be after until");
|
|
56203
|
+
}
|
|
56204
|
+
return filters;
|
|
56205
|
+
}
|
|
56206
|
+
function filterFingerprint(filters) {
|
|
56207
|
+
return createHash2("sha256").update(JSON.stringify(filters), "utf8").digest("hex");
|
|
56208
|
+
}
|
|
56209
|
+
function auditCursorSecret() {
|
|
56210
|
+
return process.env["MEMENTOS_AUDIT_CURSOR_SECRET"]?.trim() || process.env["API_KEY_SIGNING_SECRET"]?.trim() || process.env["HASNA_MEMENTOS_API_SIGNING_KEY"]?.trim() || process.env["HASNA_API_SIGNING_KEY"]?.trim() || PROCESS_CURSOR_SECRET;
|
|
56211
|
+
}
|
|
56212
|
+
function cursorMac(payload, secret) {
|
|
56213
|
+
return createHmac("sha256", secret).update(CURSOR_MAC_DOMAIN, "utf8").update(JSON.stringify(payload), "utf8").digest();
|
|
56214
|
+
}
|
|
56215
|
+
function encodeAuditCursor(payload, secret) {
|
|
56216
|
+
const mac = cursorMac(payload, secret).toString("base64url");
|
|
56217
|
+
return Buffer.from(JSON.stringify({ ...payload, mac }), "utf8").toString("base64url");
|
|
56218
|
+
}
|
|
56219
|
+
function decodeAuditCursor(cursor, contract, filters, secret) {
|
|
56220
|
+
if (!cursor || Buffer.byteLength(cursor, "utf8") > MAX_CURSOR_BYTES || !/^[A-Za-z0-9_-]+$/.test(cursor)) {
|
|
56221
|
+
throw new AuditContractError("cursor is not a valid bounded audit cursor");
|
|
56222
|
+
}
|
|
56223
|
+
let raw;
|
|
56224
|
+
try {
|
|
56225
|
+
raw = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
56226
|
+
} catch {
|
|
56227
|
+
throw new AuditContractError("cursor is not valid base64url JSON");
|
|
56228
|
+
}
|
|
56229
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
56230
|
+
throw new AuditContractError("cursor payload must be an object");
|
|
56231
|
+
const object2 = raw;
|
|
56232
|
+
const keys = Object.keys(object2).sort();
|
|
56233
|
+
const expectedKeys = [
|
|
56234
|
+
"after_created_at",
|
|
56235
|
+
"after_id",
|
|
56236
|
+
"consumed",
|
|
56237
|
+
"contract",
|
|
56238
|
+
"filter_fingerprint",
|
|
56239
|
+
"mac",
|
|
56240
|
+
"snapshot_created_at",
|
|
56241
|
+
"snapshot_id",
|
|
56242
|
+
"snapshot_total",
|
|
56243
|
+
"v"
|
|
56244
|
+
].sort();
|
|
56245
|
+
if (JSON.stringify(keys) !== JSON.stringify(expectedKeys))
|
|
56246
|
+
throw new AuditContractError("cursor payload fields are invalid");
|
|
56247
|
+
const timestamp = (value, label) => value === null ? null : canonicalAuditTimestamp(value, label);
|
|
56248
|
+
const identifier = (value, label) => {
|
|
56249
|
+
if (value === null)
|
|
56250
|
+
return null;
|
|
56251
|
+
if (typeof value !== "string")
|
|
56252
|
+
throw new AuditContractError(`${label} must be a string or null`);
|
|
56253
|
+
return printableIdentifier(value, label);
|
|
56254
|
+
};
|
|
56255
|
+
const payload = {
|
|
56256
|
+
v: object2.v === AUDIT_CURSOR_VERSION ? AUDIT_CURSOR_VERSION : (() => {
|
|
56257
|
+
throw new AuditContractError("unsupported cursor version");
|
|
56258
|
+
})(),
|
|
56259
|
+
contract: object2.contract === AUDIT_TRAIL_CONTRACT || object2.contract === AUDIT_EXPORT_CONTRACT ? object2.contract : (() => {
|
|
56260
|
+
throw new AuditContractError("cursor contract is invalid");
|
|
56261
|
+
})(),
|
|
56262
|
+
snapshot_created_at: timestamp(object2.snapshot_created_at, "cursor.snapshot_created_at"),
|
|
56263
|
+
snapshot_id: identifier(object2.snapshot_id, "cursor.snapshot_id"),
|
|
56264
|
+
after_created_at: timestamp(object2.after_created_at, "cursor.after_created_at"),
|
|
56265
|
+
after_id: identifier(object2.after_id, "cursor.after_id"),
|
|
56266
|
+
snapshot_total: Number.isSafeInteger(object2.snapshot_total) && object2.snapshot_total >= 0 ? object2.snapshot_total : (() => {
|
|
56267
|
+
throw new AuditContractError("cursor.snapshot_total must be a non-negative safe integer");
|
|
56268
|
+
})(),
|
|
56269
|
+
consumed: Number.isSafeInteger(object2.consumed) && object2.consumed >= 0 ? object2.consumed : (() => {
|
|
56270
|
+
throw new AuditContractError("cursor.consumed must be a non-negative safe integer");
|
|
56271
|
+
})(),
|
|
56272
|
+
filter_fingerprint: typeof object2.filter_fingerprint === "string" ? object2.filter_fingerprint : ""
|
|
56273
|
+
};
|
|
56274
|
+
const encodedMac = object2.mac;
|
|
56275
|
+
if (typeof encodedMac !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(encodedMac)) {
|
|
56276
|
+
throw new AuditContractError("cursor authentication code is invalid");
|
|
56277
|
+
}
|
|
56278
|
+
const providedMac = Buffer.from(encodedMac, "base64url");
|
|
56279
|
+
const expectedMac = cursorMac(payload, secret);
|
|
56280
|
+
if (providedMac.length !== expectedMac.length || !timingSafeEqual(providedMac, expectedMac) || payload.contract !== contract || payload.filter_fingerprint !== filterFingerprint(filters)) {
|
|
56281
|
+
throw new AuditContractError("cursor authentication or query binding failed");
|
|
56282
|
+
}
|
|
56283
|
+
if (!payload.snapshot_created_at || !payload.snapshot_id || !payload.after_created_at || !payload.after_id) {
|
|
56284
|
+
throw new AuditContractError("continuation cursor is missing its snapshot or ordering boundary");
|
|
56285
|
+
}
|
|
56286
|
+
if (payload.after_created_at > payload.snapshot_created_at || payload.after_created_at === payload.snapshot_created_at && payload.after_id > payload.snapshot_id) {
|
|
56287
|
+
throw new AuditContractError("cursor continuation lies outside its snapshot boundary");
|
|
56288
|
+
}
|
|
56289
|
+
return payload;
|
|
56290
|
+
}
|
|
56291
|
+
function dbTimestamp(value, db) {
|
|
56292
|
+
if (db instanceof PgAdapter)
|
|
56293
|
+
return new Date(value);
|
|
56294
|
+
const withoutZone = value.slice(0, -1).replace("T", " ");
|
|
56295
|
+
return withoutZone.endsWith(".000") ? withoutZone.slice(0, -4) : withoutZone;
|
|
56296
|
+
}
|
|
56297
|
+
function normalizedDbEntry(row) {
|
|
56298
|
+
let changes = row.changes;
|
|
56299
|
+
if (typeof changes === "string") {
|
|
56300
|
+
try {
|
|
56301
|
+
changes = JSON.parse(changes || "{}");
|
|
56302
|
+
} catch {
|
|
56303
|
+
throw new AuditContractError("audit database row changes is not valid JSON");
|
|
56304
|
+
}
|
|
56305
|
+
}
|
|
56306
|
+
return validateAuditEntry({
|
|
56307
|
+
id: row.id,
|
|
56308
|
+
memory_id: row.memory_id,
|
|
56309
|
+
memory_key: row.memory_key ?? null,
|
|
56310
|
+
operation: row.operation,
|
|
56311
|
+
agent_id: row.agent_id ?? null,
|
|
56312
|
+
old_value_hash: row.old_value_hash ?? null,
|
|
56313
|
+
new_value_hash: row.new_value_hash ?? null,
|
|
56314
|
+
changes: changes ?? {},
|
|
56315
|
+
created_at: canonicalAuditTimestamp(row.created_at, "audit database row created_at")
|
|
56316
|
+
}, "audit database row");
|
|
56317
|
+
}
|
|
56318
|
+
function buildFilterSql(filters, db) {
|
|
56319
|
+
const conditions = [];
|
|
56320
|
+
const params = [];
|
|
56321
|
+
if (filters.memory_id) {
|
|
56322
|
+
conditions.push("memory_id = ?");
|
|
56323
|
+
params.push(filters.memory_id);
|
|
56324
|
+
}
|
|
56325
|
+
if (filters.since) {
|
|
56326
|
+
conditions.push("created_at >= ?");
|
|
56327
|
+
params.push(dbTimestamp(filters.since, db));
|
|
56328
|
+
}
|
|
56329
|
+
if (filters.until) {
|
|
56330
|
+
conditions.push("created_at <= ?");
|
|
56331
|
+
params.push(dbTimestamp(filters.until, db));
|
|
56332
|
+
}
|
|
56333
|
+
if (filters.operation) {
|
|
56334
|
+
conditions.push("operation = ?");
|
|
56335
|
+
params.push(filters.operation);
|
|
56336
|
+
}
|
|
56337
|
+
if (filters.agent_id) {
|
|
56338
|
+
conditions.push("agent_id = ?");
|
|
56339
|
+
params.push(filters.agent_id);
|
|
56340
|
+
}
|
|
56341
|
+
return { conditions, params };
|
|
56342
|
+
}
|
|
56343
|
+
function auditSortExpression(db) {
|
|
56344
|
+
return db instanceof PgAdapter ? "date_trunc('milliseconds', created_at)" : "created_at";
|
|
56345
|
+
}
|
|
56346
|
+
function boundarySql(payload, db) {
|
|
56347
|
+
const conditions = [];
|
|
56348
|
+
const params = [];
|
|
56349
|
+
const createdAt = auditSortExpression(db);
|
|
56350
|
+
if (payload.snapshot_created_at && payload.snapshot_id) {
|
|
56351
|
+
const value = dbTimestamp(payload.snapshot_created_at, db);
|
|
56352
|
+
conditions.push(`(${createdAt} < ? OR (${createdAt} = ? AND id <= ?))`);
|
|
56353
|
+
params.push(value, value, payload.snapshot_id);
|
|
56354
|
+
}
|
|
56355
|
+
if (payload.after_created_at && payload.after_id) {
|
|
56356
|
+
const value = dbTimestamp(payload.after_created_at, db);
|
|
56357
|
+
conditions.push(`(${createdAt} < ? OR (${createdAt} = ? AND id < ?))`);
|
|
56358
|
+
params.push(value, value, payload.after_id);
|
|
56359
|
+
}
|
|
56360
|
+
return { conditions, params };
|
|
56361
|
+
}
|
|
56362
|
+
function whereClause(conditions) {
|
|
56363
|
+
return conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
56364
|
+
}
|
|
56365
|
+
function numberFromDb(value, label) {
|
|
56366
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
56367
|
+
if (!Number.isSafeInteger(number) || number < 0)
|
|
56368
|
+
throw new AuditContractError(`${label} is not a non-negative safe integer`);
|
|
56369
|
+
return number;
|
|
56370
|
+
}
|
|
56371
|
+
function readAuditPage(contract, filters, options, db) {
|
|
56372
|
+
const limit = safeLimit(options.limit, contract === AUDIT_TRAIL_CONTRACT ? DEFAULT_TRAIL_LIMIT : DEFAULT_EXPORT_LIMIT);
|
|
56373
|
+
const requestedCursor = options.cursor ?? null;
|
|
56374
|
+
const secret = auditCursorSecret();
|
|
56375
|
+
const continuation = requestedCursor ? decodeAuditCursor(requestedCursor, contract, filters, secret) : null;
|
|
56376
|
+
return db.transaction(() => {
|
|
56377
|
+
const base = buildFilterSql(filters, db);
|
|
56378
|
+
let snapshotCreatedAt = continuation?.snapshot_created_at ?? null;
|
|
56379
|
+
let snapshotId = continuation?.snapshot_id ?? null;
|
|
56380
|
+
if (!continuation) {
|
|
56381
|
+
const createdAt = auditSortExpression(db);
|
|
56382
|
+
const snapshotRow = db.query(`SELECT ${createdAt} AS created_at, id FROM memory_audit_log ${whereClause(base.conditions)} ORDER BY ${createdAt} DESC, id DESC LIMIT 1`).get(...base.params);
|
|
56383
|
+
if (snapshotRow) {
|
|
56384
|
+
snapshotCreatedAt = canonicalAuditTimestamp(snapshotRow.created_at, "audit snapshot created_at");
|
|
56385
|
+
snapshotId = printableIdentifier(String(snapshotRow.id ?? ""), "audit snapshot id");
|
|
56386
|
+
}
|
|
56387
|
+
}
|
|
56388
|
+
const snapshotBoundary = boundarySql({
|
|
56389
|
+
snapshot_created_at: snapshotCreatedAt,
|
|
56390
|
+
snapshot_id: snapshotId,
|
|
56391
|
+
after_created_at: null,
|
|
56392
|
+
after_id: null
|
|
56393
|
+
}, db);
|
|
56394
|
+
const countConditions = [...base.conditions, ...snapshotBoundary.conditions];
|
|
56395
|
+
const countParams = [...base.params, ...snapshotBoundary.params];
|
|
56396
|
+
const totalRow = db.query(`SELECT COUNT(*) AS count FROM memory_audit_log ${whereClause(countConditions)}`).get(...countParams);
|
|
56397
|
+
const total = numberFromDb(totalRow?.count ?? 0, "audit total");
|
|
56398
|
+
if (continuation && continuation.snapshot_total !== total) {
|
|
56399
|
+
throw new AuditContractError("audit snapshot changed while following the cursor; restart from the first page");
|
|
56400
|
+
}
|
|
56401
|
+
const pageBoundary = boundarySql({
|
|
56402
|
+
snapshot_created_at: snapshotCreatedAt,
|
|
56403
|
+
snapshot_id: snapshotId,
|
|
56404
|
+
after_created_at: continuation?.after_created_at ?? null,
|
|
56405
|
+
after_id: continuation?.after_id ?? null
|
|
56406
|
+
}, db);
|
|
56407
|
+
const pageConditions = [...base.conditions, ...pageBoundary.conditions];
|
|
56408
|
+
const pageParams = [...base.params, ...pageBoundary.params];
|
|
56409
|
+
const rawRows = db.query(`SELECT id, memory_id, memory_key, operation, agent_id, old_value_hash, new_value_hash, changes, created_at
|
|
56410
|
+
FROM memory_audit_log ${whereClause(pageConditions)}
|
|
56411
|
+
ORDER BY ${auditSortExpression(db)} DESC, id DESC LIMIT ?`).all(...pageParams, limit + 1);
|
|
56412
|
+
const hasExtra = rawRows.length > limit;
|
|
56413
|
+
const rows = hasExtra ? rawRows.slice(0, limit) : rawRows;
|
|
56414
|
+
const entries = rows.map(normalizedDbEntry);
|
|
56415
|
+
const consumedBefore = continuation?.consumed ?? 0;
|
|
56416
|
+
const consumed = consumedBefore + entries.length;
|
|
56417
|
+
if (consumed > total || hasExtra !== consumed < total) {
|
|
56418
|
+
throw new AuditContractError("audit page changed inside its database snapshot");
|
|
56419
|
+
}
|
|
56420
|
+
const hasMore = consumed < total;
|
|
56421
|
+
const last = entries.at(-1);
|
|
56422
|
+
const nextCursor = hasMore && last && snapshotCreatedAt && snapshotId ? encodeAuditCursor({
|
|
56423
|
+
v: AUDIT_CURSOR_VERSION,
|
|
56424
|
+
contract,
|
|
56425
|
+
snapshot_created_at: snapshotCreatedAt,
|
|
56426
|
+
snapshot_id: snapshotId,
|
|
56427
|
+
after_created_at: last.created_at,
|
|
56428
|
+
after_id: last.id,
|
|
56429
|
+
snapshot_total: total,
|
|
56430
|
+
consumed,
|
|
56431
|
+
filter_fingerprint: filterFingerprint(filters)
|
|
56432
|
+
}, secret) : null;
|
|
56433
|
+
const page = {
|
|
56434
|
+
contract,
|
|
56435
|
+
entries,
|
|
56436
|
+
count: entries.length,
|
|
56437
|
+
total,
|
|
56438
|
+
limit,
|
|
56439
|
+
cursor: requestedCursor,
|
|
56440
|
+
next_cursor: nextCursor,
|
|
56441
|
+
consumed,
|
|
56442
|
+
has_more: hasMore,
|
|
56443
|
+
complete: requestedCursor === null && !hasMore,
|
|
56444
|
+
snapshot_at: snapshotCreatedAt ?? now(),
|
|
56445
|
+
filters,
|
|
56446
|
+
sort: { field: "created_at", direction: "desc", tie_breaker: "id" }
|
|
56447
|
+
};
|
|
56448
|
+
return validateAuditPage(page, { contract, cursor: requestedCursor, filters, limit });
|
|
56449
|
+
});
|
|
56450
|
+
}
|
|
56451
|
+
function hostedAuditPage(contract, path, filters, options) {
|
|
56452
|
+
const limit = safeLimit(options.limit, contract === AUDIT_TRAIL_CONTRACT ? DEFAULT_TRAIL_LIMIT : DEFAULT_EXPORT_LIMIT);
|
|
56453
|
+
const cursor = options.cursor ?? null;
|
|
56454
|
+
const operation = `GET ${path.split("?")[0]}`;
|
|
56455
|
+
const { data } = apiJson("GET", path);
|
|
56456
|
+
try {
|
|
56457
|
+
return validateAuditPage(data, { contract, cursor, filters, limit });
|
|
56458
|
+
} catch (error) {
|
|
56459
|
+
return protocol(operation, error);
|
|
56460
|
+
}
|
|
56461
|
+
}
|
|
56462
|
+
function getMemoryAuditTrailPage(memoryId, options = {}, db) {
|
|
56463
|
+
const filters = normalizeAuditFilters({ memory_id: memoryId });
|
|
56464
|
+
const limit = safeLimit(options.limit, DEFAULT_TRAIL_LIMIT);
|
|
56465
|
+
if (!db && isApiMode()) {
|
|
56466
|
+
const query = toQuery({ limit, cursor: options.cursor });
|
|
56467
|
+
return hostedAuditPage(AUDIT_TRAIL_CONTRACT, `/memories/${encodeURIComponent(filters.memory_id)}/audit-trail${query}`, filters, { limit, cursor: options.cursor });
|
|
56468
|
+
}
|
|
56469
|
+
return readAuditPage(AUDIT_TRAIL_CONTRACT, filters, { limit, cursor: options.cursor }, db ?? getDatabase());
|
|
56470
|
+
}
|
|
56471
|
+
function exportAuditLogPage(options = {}, db) {
|
|
56472
|
+
const filters = normalizeAuditFilters(options);
|
|
56473
|
+
const limit = safeLimit(options.limit, DEFAULT_EXPORT_LIMIT);
|
|
56474
|
+
if (!db && isApiMode()) {
|
|
56475
|
+
const query = toQuery({
|
|
56476
|
+
since: filters.since ?? undefined,
|
|
56477
|
+
until: filters.until ?? undefined,
|
|
56478
|
+
operation: filters.operation ?? undefined,
|
|
56479
|
+
agent_id: filters.agent_id ?? undefined,
|
|
56480
|
+
limit,
|
|
56481
|
+
cursor: options.cursor
|
|
56482
|
+
});
|
|
56483
|
+
return hostedAuditPage(AUDIT_EXPORT_CONTRACT, `/audit/export${query}`, filters, { limit, cursor: options.cursor });
|
|
56484
|
+
}
|
|
56485
|
+
return readAuditPage(AUDIT_EXPORT_CONTRACT, filters, { limit, cursor: options.cursor }, db ?? getDatabase());
|
|
56486
|
+
}
|
|
56487
|
+
function getMemoryAuditTrail(memoryId, limit = DEFAULT_TRAIL_LIMIT, db) {
|
|
56488
|
+
return getMemoryAuditTrailPage(memoryId, { limit }, db).entries;
|
|
56489
|
+
}
|
|
56490
|
+
function exportAuditLog(options = {}, db) {
|
|
56491
|
+
return exportAuditLogPage(options, db).entries;
|
|
56492
|
+
}
|
|
56493
|
+
function auditStatsFromDb(db) {
|
|
56494
|
+
const snapshotAt = now();
|
|
56495
|
+
const recentSince = new Date(new Date(snapshotAt).getTime() - 24 * 60 * 60 * 1000).toISOString();
|
|
56496
|
+
const snapshotParam = dbTimestamp(snapshotAt, db);
|
|
56497
|
+
const recentParam = dbTimestamp(recentSince, db);
|
|
56498
|
+
const row = db.query(`SELECT
|
|
56499
|
+
COUNT(*) AS total_entries,
|
|
56500
|
+
SUM(CASE WHEN operation = 'create' THEN 1 ELSE 0 END) AS create_count,
|
|
56501
|
+
SUM(CASE WHEN operation = 'update' THEN 1 ELSE 0 END) AS update_count,
|
|
56502
|
+
SUM(CASE WHEN operation = 'delete' THEN 1 ELSE 0 END) AS delete_count,
|
|
56503
|
+
SUM(CASE WHEN operation = 'archive' THEN 1 ELSE 0 END) AS archive_count,
|
|
56504
|
+
SUM(CASE WHEN operation = 'restore' THEN 1 ELSE 0 END) AS restore_count,
|
|
56505
|
+
SUM(CASE WHEN operation = 'read' THEN 1 ELSE 0 END) AS read_count,
|
|
56506
|
+
SUM(CASE WHEN created_at >= ? AND created_at <= ? THEN 1 ELSE 0 END) AS recent_24h
|
|
56507
|
+
FROM memory_audit_log
|
|
56508
|
+
WHERE created_at <= ?`).get(recentParam, snapshotParam, snapshotParam);
|
|
56509
|
+
const byOperation = Object.fromEntries(AUDIT_OPERATIONS.map((operation) => [
|
|
56510
|
+
operation,
|
|
56511
|
+
numberFromDb(row?.[`${operation}_count`] ?? 0, `audit ${operation} count`)
|
|
56512
|
+
]));
|
|
56513
|
+
return validateAuditStats({
|
|
56514
|
+
contract: AUDIT_STATS_CONTRACT,
|
|
56515
|
+
total_entries: numberFromDb(row?.total_entries ?? 0, "audit total_entries"),
|
|
56516
|
+
by_operation: byOperation,
|
|
56517
|
+
recent_24h: numberFromDb(row?.recent_24h ?? 0, "audit recent_24h"),
|
|
56518
|
+
snapshot_at: snapshotAt
|
|
56519
|
+
});
|
|
56520
|
+
}
|
|
56521
|
+
function getAuditStats(db) {
|
|
56522
|
+
if (!db && isApiMode()) {
|
|
56523
|
+
const operation = "GET /audit/stats";
|
|
56524
|
+
const { data } = apiJson("GET", "/audit/stats");
|
|
56525
|
+
try {
|
|
56526
|
+
return validateAuditStats(data);
|
|
56527
|
+
} catch (error) {
|
|
56528
|
+
return protocol(operation, error);
|
|
56529
|
+
}
|
|
56530
|
+
}
|
|
56531
|
+
return auditStatsFromDb(db ?? getDatabase());
|
|
56532
|
+
}
|
|
56533
|
+
// src/lib/memory-lock.ts
|
|
55559
56534
|
var MEMORY_WRITE_TTL = 30;
|
|
55560
56535
|
function memoryLockId(key, scope, projectId) {
|
|
55561
56536
|
return `${scope}:${key}:${projectId ?? ""}`;
|
|
55562
56537
|
}
|
|
55563
56538
|
function acquireMemoryWriteLock(agentId, key, scope, projectId, ttlSeconds = MEMORY_WRITE_TTL, db) {
|
|
55564
|
-
|
|
55565
|
-
return acquireLock(agentId, "memory", memoryLockId(key, scope, projectId), "exclusive", ttlSeconds, d);
|
|
56539
|
+
return acquireLock(agentId, "memory", memoryLockId(key, scope, projectId), "exclusive", ttlSeconds, db);
|
|
55566
56540
|
}
|
|
55567
56541
|
function releaseMemoryWriteLock(lockId, agentId, db) {
|
|
55568
|
-
|
|
55569
|
-
return releaseLock(lockId, agentId, d);
|
|
56542
|
+
return releaseLock(lockId, agentId, db);
|
|
55570
56543
|
}
|
|
55571
56544
|
function checkMemoryWriteLock(key, scope, projectId, db) {
|
|
55572
|
-
const
|
|
55573
|
-
const locks = checkLock("memory", memoryLockId(key, scope, projectId), "exclusive", d);
|
|
56545
|
+
const locks = checkLock("memory", memoryLockId(key, scope, projectId), "exclusive", db);
|
|
55574
56546
|
return locks[0] ?? null;
|
|
55575
56547
|
}
|
|
55576
56548
|
function withMemoryLock(agentId, key, scope, projectId, fn, ttlSeconds = MEMORY_WRITE_TTL, db) {
|
|
55577
|
-
const
|
|
55578
|
-
const lock = acquireMemoryWriteLock(agentId, key, scope, projectId, ttlSeconds, d);
|
|
56549
|
+
const lock = acquireMemoryWriteLock(agentId, key, scope, projectId, ttlSeconds, db);
|
|
55579
56550
|
if (!lock) {
|
|
55580
|
-
const existing = checkMemoryWriteLock(key, scope, projectId,
|
|
56551
|
+
const existing = checkMemoryWriteLock(key, scope, projectId, db);
|
|
55581
56552
|
throw new MemoryLockConflictError(key, scope, existing?.agent_id ?? "unknown");
|
|
55582
56553
|
}
|
|
55583
56554
|
try {
|
|
55584
56555
|
return fn();
|
|
55585
56556
|
} finally {
|
|
55586
|
-
releaseLock(lock.id, agentId,
|
|
56557
|
+
releaseLock(lock.id, agentId, db);
|
|
55587
56558
|
}
|
|
55588
56559
|
}
|
|
55589
56560
|
|
|
@@ -55667,7 +56638,7 @@ function focusFilterSQL(agentId, projectId) {
|
|
|
55667
56638
|
// src/db/projects.ts
|
|
55668
56639
|
init_database();
|
|
55669
56640
|
init_api_mode();
|
|
55670
|
-
import { createHash as
|
|
56641
|
+
import { createHash as createHash3 } from "crypto";
|
|
55671
56642
|
function parseProjectRow2(row) {
|
|
55672
56643
|
return {
|
|
55673
56644
|
id: row["id"],
|
|
@@ -55729,7 +56700,7 @@ function canonicalProjectUpdateJson(value) {
|
|
|
55729
56700
|
return JSON.stringify(canonicalizeProjectUpdateValue(value));
|
|
55730
56701
|
}
|
|
55731
56702
|
function digestProjectUpdateValue(value) {
|
|
55732
|
-
return
|
|
56703
|
+
return createHash3("sha256").update(canonicalProjectUpdateJson(value)).digest("hex");
|
|
55733
56704
|
}
|
|
55734
56705
|
function timestampString2(value) {
|
|
55735
56706
|
return value instanceof Date ? value.toISOString() : String(value);
|
|
@@ -56152,7 +57123,7 @@ function getProjectUpdateReceipt(id, receiptId, identity = projectUpdateAuthorit
|
|
|
56152
57123
|
return receipt;
|
|
56153
57124
|
}
|
|
56154
57125
|
// src/project-registration/authority.ts
|
|
56155
|
-
import { createHash as
|
|
57126
|
+
import { createHash as createHash4 } from "crypto";
|
|
56156
57127
|
import { resolve as resolve3 } from "path";
|
|
56157
57128
|
|
|
56158
57129
|
// src/project-registration/project-references.ts
|
|
@@ -56243,10 +57214,10 @@ function canonicalize2(value) {
|
|
|
56243
57214
|
return out;
|
|
56244
57215
|
}
|
|
56245
57216
|
function digestMementosProjectRegistrationValue(value) {
|
|
56246
|
-
return
|
|
57217
|
+
return createHash4("sha256").update(canonicalMementosProjectRegistrationJson(value)).digest("hex");
|
|
56247
57218
|
}
|
|
56248
57219
|
function digestOwnedPath(path) {
|
|
56249
|
-
return
|
|
57220
|
+
return createHash4("sha256").update(path).digest("hex");
|
|
56250
57221
|
}
|
|
56251
57222
|
function deriveMementosProjectRegistrationIdempotencyKey(input) {
|
|
56252
57223
|
return `prk_${digestMementosProjectRegistrationValue({
|
|
@@ -56317,7 +57288,7 @@ function requireString(value, field, options = {}) {
|
|
|
56317
57288
|
}
|
|
56318
57289
|
return value;
|
|
56319
57290
|
}
|
|
56320
|
-
function
|
|
57291
|
+
function exactKeys2(value, expected, field) {
|
|
56321
57292
|
const actual = Object.keys(value).sort();
|
|
56322
57293
|
const wanted = [...expected].sort();
|
|
56323
57294
|
if (actual.length !== wanted.length || actual.some((key, i) => key !== wanted[i])) {
|
|
@@ -56374,7 +57345,7 @@ function assertGuardedUpdateRequest(targetId, request, capability) {
|
|
|
56374
57345
|
if (!request.updates || typeof request.updates !== "object") {
|
|
56375
57346
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "guarded project updates must contain exactly one private path handle");
|
|
56376
57347
|
}
|
|
56377
|
-
|
|
57348
|
+
exactKeys2(request.updates, ["path"], "updates");
|
|
56378
57349
|
return { identity, path: ownedPath(request.updates.path) };
|
|
56379
57350
|
}
|
|
56380
57351
|
function publicGuardedUpdateReceipt(receipt, capability) {
|
|
@@ -56698,7 +57669,7 @@ function assertForwardRequest2(request, capability) {
|
|
|
56698
57669
|
if (request.direction !== "forward" || request.accepted_receipt !== undefined) {
|
|
56699
57670
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional create requires a forward request without an accepted receipt");
|
|
56700
57671
|
}
|
|
56701
|
-
|
|
57672
|
+
exactKeys2(request.desired, [
|
|
56702
57673
|
"name",
|
|
56703
57674
|
"source_project_id",
|
|
56704
57675
|
"source_project_slug",
|
|
@@ -56732,7 +57703,7 @@ function assertInverseRequest(request, capability) {
|
|
|
56732
57703
|
if (accepted.authority !== "mementos" || accepted.route !== capability.route || accepted.package_version !== capability.package_version || accepted.authority_id !== capability.authority_id || accepted.tenant_id !== capability.tenant_id || accepted.corpus_id !== capability.corpus_id || accepted.operation_id !== request.operation_id || accepted.step_id !== request.step_id || accepted.resource_kind !== "project" || accepted.direction !== "forward" || accepted.outcome !== "accepted" || !accepted.created_by_operation || !accepted.target_id || !accepted.result_revision || !accepted.result_digest || request.target_selector !== accepted.target_id) {
|
|
56733
57704
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "accepted receipt is not an exact attempt-created forward receipt");
|
|
56734
57705
|
}
|
|
56735
|
-
|
|
57706
|
+
exactKeys2(request.desired, ["accepted_receipt_id", "target_id"], "inverse desired");
|
|
56736
57707
|
if (request.desired["accepted_receipt_id"] !== accepted.receipt_id || request.desired["target_id"] !== accepted.target_id) {
|
|
56737
57708
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "inverse desired payload does not bind the accepted receipt");
|
|
56738
57709
|
}
|
|
@@ -56826,8 +57797,8 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
56826
57797
|
target_id: accepted.target_id
|
|
56827
57798
|
});
|
|
56828
57799
|
}
|
|
56829
|
-
const
|
|
56830
|
-
if (
|
|
57800
|
+
const record2 = projectRecord(this.db, project);
|
|
57801
|
+
if (record2.revision !== accepted.result_revision || record2.digest !== accepted.result_digest) {
|
|
56831
57802
|
return this.terminal(request, callDigest, "accepted_target_drifted", {
|
|
56832
57803
|
target_id: accepted.target_id
|
|
56833
57804
|
});
|
|
@@ -56854,7 +57825,7 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
56854
57825
|
if (project.updated_at !== receipt.result_revision) {
|
|
56855
57826
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "guarded project result revision did not match its immutable receipt");
|
|
56856
57827
|
}
|
|
56857
|
-
const
|
|
57828
|
+
const record2 = {
|
|
56858
57829
|
target_id: targetId2,
|
|
56859
57830
|
revision: receipt.result_revision,
|
|
56860
57831
|
digest: receipt.result_digest
|
|
@@ -56862,7 +57833,7 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
56862
57833
|
return withBoundedResponseControl({
|
|
56863
57834
|
dry_run: false,
|
|
56864
57835
|
applied: true,
|
|
56865
|
-
record,
|
|
57836
|
+
record: record2,
|
|
56866
57837
|
receipt: publicGuardedUpdateReceipt(receipt, this.capabilityValue)
|
|
56867
57838
|
}, bounds, startedAt);
|
|
56868
57839
|
}
|
|
@@ -56991,12 +57962,12 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
56991
57962
|
if (!project || project.path !== path) {
|
|
56992
57963
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CONFLICT", "project write did not read back by its full id");
|
|
56993
57964
|
}
|
|
56994
|
-
const
|
|
57965
|
+
const record2 = projectRecord(this.db, project);
|
|
56995
57966
|
const acceptedReceipt = makeReceipt2(request, this.capabilityValue, callDigest, now2, {
|
|
56996
57967
|
outcome: "accepted",
|
|
56997
|
-
target_id:
|
|
56998
|
-
result_revision:
|
|
56999
|
-
result_digest:
|
|
57968
|
+
target_id: record2.target_id,
|
|
57969
|
+
result_revision: record2.revision,
|
|
57970
|
+
result_digest: record2.digest,
|
|
57000
57971
|
created_by_operation: true
|
|
57001
57972
|
});
|
|
57002
57973
|
this.writeBoundary("before_receipt_write", request);
|
|
@@ -57036,9 +58007,9 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
57036
58007
|
if (!project || project.path !== path) {
|
|
57037
58008
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", "exact full-id project readback did not resolve the owned path");
|
|
57038
58009
|
}
|
|
57039
|
-
const
|
|
57040
|
-
assertWithinBounds(
|
|
57041
|
-
return
|
|
58010
|
+
const record2 = projectRecord(this.db, project);
|
|
58011
|
+
assertWithinBounds(record2, request, startedAt);
|
|
58012
|
+
return record2;
|
|
57042
58013
|
}
|
|
57043
58014
|
async lookupReceipt(request) {
|
|
57044
58015
|
const startedAt = Date.now();
|
|
@@ -57816,103 +58787,401 @@ function getMementosProjectResourceExact(projectId, resourceKind, stableId, db,
|
|
|
57816
58787
|
}
|
|
57817
58788
|
// src/db/machines.ts
|
|
57818
58789
|
init_database();
|
|
58790
|
+
init_api_mode();
|
|
58791
|
+
import { createHash as createHash5 } from "crypto";
|
|
57819
58792
|
import { hostname, platform } from "os";
|
|
57820
|
-
|
|
58793
|
+
var MACHINE_REGISTRATION_CONTRACT = "mementos.machine-registration.v1";
|
|
58794
|
+
var MACHINE_LIST_CONTRACT = "mementos.machines.v1";
|
|
58795
|
+
var MACHINE_MUTATION_CONTRACT = "mementos.machine-mutation.v1";
|
|
58796
|
+
var MACHINE_TOUCH_CONTRACT = "mementos.machine-touch.v1";
|
|
58797
|
+
|
|
58798
|
+
class MachineRegistryError extends Error {
|
|
58799
|
+
code;
|
|
58800
|
+
constructor(code, message) {
|
|
58801
|
+
super(message);
|
|
58802
|
+
this.code = code;
|
|
58803
|
+
this.name = "MachineRegistryError";
|
|
58804
|
+
}
|
|
58805
|
+
}
|
|
58806
|
+
var ISO_UTC_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
58807
|
+
function canonicalTimestamp(value, operation, field) {
|
|
58808
|
+
const stringValue = value instanceof Date ? value.toISOString() : value;
|
|
58809
|
+
if (typeof stringValue !== "string" || !ISO_UTC_TIMESTAMP.test(stringValue)) {
|
|
58810
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a canonical UTC ISO-8601 timestamp`);
|
|
58811
|
+
}
|
|
58812
|
+
const parsed = new Date(stringValue);
|
|
58813
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== stringValue) {
|
|
58814
|
+
throw new MementosApiProtocolError(operation, `expected '${field}' to be a real calendar timestamp`);
|
|
58815
|
+
}
|
|
58816
|
+
return stringValue;
|
|
58817
|
+
}
|
|
58818
|
+
function machineFromDb(row, operation = "machine database row") {
|
|
57821
58819
|
if (!row)
|
|
57822
58820
|
return null;
|
|
58821
|
+
if (typeof row.id !== "string" || !row.id || typeof row.name !== "string" || !row.name || row.name.length > 128 || /[\u0000-\u001f\u007f]/.test(row.name) || typeof row.hostname !== "string" || !row.hostname || typeof row.platform !== "string" || !row.platform) {
|
|
58822
|
+
throw new Error(`${operation} is missing or violates required machine identity fields`);
|
|
58823
|
+
}
|
|
58824
|
+
const canonicalHostname = normalizeMachineHostname(row.hostname);
|
|
58825
|
+
const canonicalPlatform = normalizeMachinePlatform(row.platform);
|
|
58826
|
+
if (canonicalHostname !== row.hostname || canonicalPlatform !== row.platform) {
|
|
58827
|
+
throw new Error(`${operation} contains noncanonical hostname or platform data`);
|
|
58828
|
+
}
|
|
58829
|
+
if (!["boolean", "number"].includes(typeof row.is_primary)) {
|
|
58830
|
+
throw new Error(`${operation} has an invalid is_primary value`);
|
|
58831
|
+
}
|
|
58832
|
+
const createdValue = row.created_at instanceof Date ? row.created_at : new Date(String(row.created_at ?? ""));
|
|
58833
|
+
const lastSeenValue = row.last_seen_at instanceof Date ? row.last_seen_at : new Date(String(row.last_seen_at ?? ""));
|
|
58834
|
+
if (!Number.isFinite(createdValue.getTime()) || !Number.isFinite(lastSeenValue.getTime())) {
|
|
58835
|
+
throw new Error(`${operation} has an invalid timestamp`);
|
|
58836
|
+
}
|
|
58837
|
+
const createdAt = createdValue.toISOString();
|
|
58838
|
+
const lastSeenAt = lastSeenValue.toISOString();
|
|
58839
|
+
if (lastSeenAt < createdAt)
|
|
58840
|
+
throw new Error(`${operation} has last_seen_at before created_at`);
|
|
57823
58841
|
return {
|
|
57824
|
-
|
|
57825
|
-
|
|
57826
|
-
|
|
58842
|
+
id: row.id,
|
|
58843
|
+
name: row.name,
|
|
58844
|
+
hostname: canonicalHostname,
|
|
58845
|
+
platform: canonicalPlatform,
|
|
58846
|
+
is_primary: Boolean(row.is_primary),
|
|
58847
|
+
created_at: createdAt,
|
|
58848
|
+
last_seen_at: lastSeenAt
|
|
58849
|
+
};
|
|
58850
|
+
}
|
|
58851
|
+
function decodeMachine(value, operation) {
|
|
58852
|
+
const object2 = expectObject(value, operation);
|
|
58853
|
+
const name = expectString(object2, "name", operation);
|
|
58854
|
+
if (name.length > 128 || /[\u0000-\u001f\u007f]/.test(name)) {
|
|
58855
|
+
throw new MementosApiProtocolError(operation, "machine.name is outside the public contract");
|
|
58856
|
+
}
|
|
58857
|
+
const rawHost = expectString(object2, "hostname", operation);
|
|
58858
|
+
const rawPlatform = expectString(object2, "platform", operation);
|
|
58859
|
+
let machineHostname;
|
|
58860
|
+
let machinePlatform;
|
|
58861
|
+
try {
|
|
58862
|
+
machineHostname = normalizeMachineHostname(rawHost);
|
|
58863
|
+
machinePlatform = normalizeMachinePlatform(rawPlatform);
|
|
58864
|
+
} catch {
|
|
58865
|
+
throw new MementosApiProtocolError(operation, "machine hostname or platform is not canonical");
|
|
58866
|
+
}
|
|
58867
|
+
if (machineHostname !== rawHost || machinePlatform !== rawPlatform) {
|
|
58868
|
+
throw new MementosApiProtocolError(operation, "machine hostname or platform is not canonical");
|
|
58869
|
+
}
|
|
58870
|
+
const createdAt = canonicalTimestamp(object2["created_at"], operation, "created_at");
|
|
58871
|
+
const lastSeenAt = canonicalTimestamp(object2["last_seen_at"], operation, "last_seen_at");
|
|
58872
|
+
if (lastSeenAt < createdAt) {
|
|
58873
|
+
throw new MementosApiProtocolError(operation, "last_seen_at precedes created_at");
|
|
58874
|
+
}
|
|
58875
|
+
return {
|
|
58876
|
+
id: expectString(object2, "id", operation),
|
|
58877
|
+
name,
|
|
58878
|
+
hostname: machineHostname,
|
|
58879
|
+
platform: machinePlatform,
|
|
58880
|
+
is_primary: expectBoolean(object2, "is_primary", operation),
|
|
58881
|
+
created_at: createdAt,
|
|
58882
|
+
last_seen_at: lastSeenAt
|
|
58883
|
+
};
|
|
58884
|
+
}
|
|
58885
|
+
function expectContract(object2, contract, operation) {
|
|
58886
|
+
if (object2["contract"] !== contract) {
|
|
58887
|
+
throw new MementosApiProtocolError(operation, `expected contract '${contract}'`);
|
|
58888
|
+
}
|
|
58889
|
+
}
|
|
58890
|
+
function decodeRegistration(value, expected) {
|
|
58891
|
+
const operation = "POST /v1/machines";
|
|
58892
|
+
const object2 = expectObject(value, operation);
|
|
58893
|
+
expectContract(object2, MACHINE_REGISTRATION_CONTRACT, operation);
|
|
58894
|
+
const machine = decodeMachine(object2["machine"], operation);
|
|
58895
|
+
const created = expectBoolean(object2, "created", operation);
|
|
58896
|
+
const identity = expectObject(object2["identity"], operation);
|
|
58897
|
+
if (identity["idempotency_key"] !== "normalized_hostname") {
|
|
58898
|
+
throw new MementosApiProtocolError(operation, "expected normalized-hostname identity semantics");
|
|
58899
|
+
}
|
|
58900
|
+
if (expectString(identity, "stable_id", operation) !== machine.id) {
|
|
58901
|
+
throw new MementosApiProtocolError(operation, "identity stable_id does not match machine.id");
|
|
58902
|
+
}
|
|
58903
|
+
const expectedHostname = normalizeMachineHostname(expected.hostname);
|
|
58904
|
+
const expectedPlatform = normalizeMachinePlatform(expected.platform);
|
|
58905
|
+
if (machine.hostname !== expectedHostname || machine.platform !== expectedPlatform) {
|
|
58906
|
+
throw new MementosApiProtocolError(operation, "registration receipt does not match the requested hostname/platform");
|
|
58907
|
+
}
|
|
58908
|
+
if (created && machine.name !== normalizeMachineName(expected.name, expectedHostname)) {
|
|
58909
|
+
throw new MementosApiProtocolError(operation, "created registration receipt does not match the requested name");
|
|
58910
|
+
}
|
|
58911
|
+
return { machine, created };
|
|
58912
|
+
}
|
|
58913
|
+
function decodeMachineList(value) {
|
|
58914
|
+
const operation = "GET /v1/machines";
|
|
58915
|
+
const object2 = expectObject(value, operation);
|
|
58916
|
+
expectContract(object2, MACHINE_LIST_CONTRACT, operation);
|
|
58917
|
+
const values = expectArray(object2["machines"], operation, "machines");
|
|
58918
|
+
const count = expectNonNegativeInteger(object2, "count", operation);
|
|
58919
|
+
if (count !== values.length) {
|
|
58920
|
+
throw new MementosApiProtocolError(operation, "count does not match machines.length");
|
|
58921
|
+
}
|
|
58922
|
+
if (object2["complete"] !== true) {
|
|
58923
|
+
throw new MementosApiProtocolError(operation, "expected complete=true");
|
|
58924
|
+
}
|
|
58925
|
+
const machines = values.map((entry, index) => decodeMachine(entry, `${operation} machines[${index}]`));
|
|
58926
|
+
const ids = new Set;
|
|
58927
|
+
const hosts = new Set;
|
|
58928
|
+
const names = new Set;
|
|
58929
|
+
let primaryCount = 0;
|
|
58930
|
+
for (const machine of machines) {
|
|
58931
|
+
if (ids.has(machine.id))
|
|
58932
|
+
throw new MementosApiProtocolError(operation, `duplicate machine id '${machine.id}'`);
|
|
58933
|
+
if (hosts.has(machine.hostname))
|
|
58934
|
+
throw new MementosApiProtocolError(operation, `duplicate machine hostname '${machine.hostname}'`);
|
|
58935
|
+
if (names.has(machine.name))
|
|
58936
|
+
throw new MementosApiProtocolError(operation, `duplicate machine name '${machine.name}'`);
|
|
58937
|
+
ids.add(machine.id);
|
|
58938
|
+
hosts.add(machine.hostname);
|
|
58939
|
+
names.add(machine.name);
|
|
58940
|
+
if (machine.is_primary)
|
|
58941
|
+
primaryCount += 1;
|
|
58942
|
+
}
|
|
58943
|
+
if (primaryCount > 1)
|
|
58944
|
+
throw new MementosApiProtocolError(operation, "more than one machine is primary");
|
|
58945
|
+
return machines;
|
|
58946
|
+
}
|
|
58947
|
+
function decodeMachineMutation(value, operation, expectedId) {
|
|
58948
|
+
const object2 = expectObject(value, operation);
|
|
58949
|
+
expectContract(object2, MACHINE_MUTATION_CONTRACT, operation);
|
|
58950
|
+
const machine = decodeMachine(object2["machine"], operation);
|
|
58951
|
+
if (machine.id !== expectedId) {
|
|
58952
|
+
throw new MementosApiProtocolError(operation, "returned machine id does not match the requested stable id");
|
|
58953
|
+
}
|
|
58954
|
+
return machine;
|
|
58955
|
+
}
|
|
58956
|
+
function normalizeMachineHostname(host) {
|
|
58957
|
+
const normalized = host.trim().replace(/\.+$/, "").toLowerCase();
|
|
58958
|
+
if (!normalized || normalized.length > 253 || /[\u0000-\u001f\u007f/\\\s]/.test(normalized)) {
|
|
58959
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine hostname must be a non-empty hostname of at most 253 characters");
|
|
58960
|
+
}
|
|
58961
|
+
return normalized;
|
|
58962
|
+
}
|
|
58963
|
+
function normalizeMachinePlatform(value) {
|
|
58964
|
+
const normalized = value.trim().toLowerCase();
|
|
58965
|
+
if (!normalized || normalized.length > 64 || !/^[a-z0-9._-]+$/.test(normalized)) {
|
|
58966
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine platform must contain 1-64 lowercase letters, digits, dots, underscores, or hyphens");
|
|
58967
|
+
}
|
|
58968
|
+
return normalized;
|
|
58969
|
+
}
|
|
58970
|
+
function normalizeMachineName(value, fallback) {
|
|
58971
|
+
const normalized = value?.trim() || fallback;
|
|
58972
|
+
if (!normalized || normalized.length > 128 || /[\u0000-\u001f\u007f]/.test(normalized)) {
|
|
58973
|
+
throw new MachineRegistryError("MACHINE_INVALID_INPUT", "Machine name must contain 1-128 printable characters");
|
|
58974
|
+
}
|
|
58975
|
+
return normalized;
|
|
57827
58976
|
}
|
|
57828
|
-
function
|
|
57829
|
-
return
|
|
58977
|
+
function getMachineById(id, db) {
|
|
58978
|
+
return machineFromDb(db.query("SELECT * FROM machines WHERE id = ?").get(id));
|
|
57830
58979
|
}
|
|
57831
|
-
function
|
|
57832
|
-
const
|
|
57833
|
-
const host =
|
|
57834
|
-
const
|
|
57835
|
-
const
|
|
57836
|
-
const
|
|
57837
|
-
|
|
57838
|
-
|
|
57839
|
-
|
|
58980
|
+
function registerMachineRecord(input, db) {
|
|
58981
|
+
const d = db ?? getDatabase();
|
|
58982
|
+
const host = normalizeMachineHostname(input.hostname);
|
|
58983
|
+
const machineName = normalizeMachineName(input.name, host);
|
|
58984
|
+
const machinePlatform = normalizeMachinePlatform(input.platform);
|
|
58985
|
+
const proposedId = uuid();
|
|
58986
|
+
const seenAt = now();
|
|
58987
|
+
try {
|
|
58988
|
+
const row = d.query(`INSERT INTO machines (id, name, hostname, platform, created_at, last_seen_at)
|
|
58989
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
58990
|
+
ON CONFLICT(hostname) DO UPDATE SET
|
|
58991
|
+
platform = excluded.platform,
|
|
58992
|
+
last_seen_at = CASE
|
|
58993
|
+
WHEN machines.last_seen_at > excluded.last_seen_at THEN machines.last_seen_at
|
|
58994
|
+
ELSE excluded.last_seen_at
|
|
58995
|
+
END
|
|
58996
|
+
RETURNING *, id = ? AS registration_created`).get(proposedId, machineName, host, machinePlatform, seenAt, seenAt, proposedId);
|
|
58997
|
+
const machine = machineFromDb(row, "machine registration upsert");
|
|
58998
|
+
if (!machine || typeof row?.registration_created !== "boolean" && typeof row?.registration_created !== "number") {
|
|
58999
|
+
throw new MachineRegistryError("MACHINE_REGISTRATION_CONFLICT", "Machine registration returned no stable identity receipt");
|
|
59000
|
+
}
|
|
59001
|
+
return { machine, created: Boolean(row.registration_created) };
|
|
59002
|
+
} catch (error) {
|
|
59003
|
+
if (error instanceof MachineRegistryError)
|
|
59004
|
+
throw error;
|
|
59005
|
+
const details = error && typeof error === "object" ? error : {};
|
|
59006
|
+
const diagnostic = `${String(details.constraint ?? "")} ${String(details.message ?? "")}`;
|
|
59007
|
+
if (/machines(?:_|\.)name|machines_name_key/i.test(diagnostic)) {
|
|
59008
|
+
throw new MachineRegistryError("MACHINE_NAME_CONFLICT", `Machine name already taken: ${machineName}`);
|
|
59009
|
+
}
|
|
59010
|
+
throw error;
|
|
57840
59011
|
}
|
|
57841
|
-
|
|
57842
|
-
|
|
57843
|
-
|
|
57844
|
-
|
|
59012
|
+
}
|
|
59013
|
+
function registerMachine(name, db) {
|
|
59014
|
+
const host = normalizeMachineHostname(hostname());
|
|
59015
|
+
const plat = normalizeMachinePlatform(platform());
|
|
59016
|
+
if (!db && isApiMode()) {
|
|
59017
|
+
const input = { name: name?.trim() || undefined, hostname: host, platform: plat };
|
|
59018
|
+
const { data } = apiJson("POST", "/machines", input);
|
|
59019
|
+
return decodeRegistration(data, input).machine;
|
|
57845
59020
|
}
|
|
57846
|
-
|
|
57847
|
-
|
|
57848
|
-
|
|
57849
|
-
|
|
57850
|
-
|
|
57851
|
-
|
|
57852
|
-
|
|
57853
|
-
|
|
57854
|
-
|
|
57855
|
-
return
|
|
57856
|
-
}
|
|
57857
|
-
function
|
|
57858
|
-
|
|
57859
|
-
|
|
57860
|
-
|
|
57861
|
-
|
|
57862
|
-
|
|
57863
|
-
|
|
57864
|
-
|
|
57865
|
-
return
|
|
57866
|
-
}
|
|
57867
|
-
function
|
|
57868
|
-
|
|
57869
|
-
|
|
57870
|
-
|
|
57871
|
-
|
|
59021
|
+
return registerMachineRecord({ name, hostname: host, platform: plat }, db).machine;
|
|
59022
|
+
}
|
|
59023
|
+
function listMachines(db) {
|
|
59024
|
+
if (!db && isApiMode()) {
|
|
59025
|
+
const { data } = apiJson("GET", "/machines");
|
|
59026
|
+
return decodeMachineList(data);
|
|
59027
|
+
}
|
|
59028
|
+
const d = db ?? getDatabase();
|
|
59029
|
+
const rows = d.query("SELECT * FROM machines ORDER BY is_primary DESC, last_seen_at DESC, created_at ASC, id ASC").all();
|
|
59030
|
+
return rows.map((row) => machineFromDb(row));
|
|
59031
|
+
}
|
|
59032
|
+
function getMachine(idOrName, db) {
|
|
59033
|
+
if (!db && isApiMode()) {
|
|
59034
|
+
const { status, data } = apiJson("GET", `/machines/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
|
|
59035
|
+
if (status === 404)
|
|
59036
|
+
return null;
|
|
59037
|
+
return decodeMachineMutation(data, "GET /v1/machines/:id", idOrName);
|
|
59038
|
+
}
|
|
59039
|
+
const d = db ?? getDatabase();
|
|
59040
|
+
return machineFromDb(d.query("SELECT * FROM machines WHERE id = ? OR name = ?").get(idOrName, idOrName));
|
|
59041
|
+
}
|
|
59042
|
+
function renameMachine(id, newName, db) {
|
|
59043
|
+
const normalizedName = normalizeMachineName(newName, "");
|
|
59044
|
+
if (!db && isApiMode()) {
|
|
59045
|
+
const { data } = apiJson("PATCH", `/machines/${encodeURIComponent(id)}`, { name: normalizedName });
|
|
59046
|
+
const machine = decodeMachineMutation(data, "PATCH /v1/machines/:id", id);
|
|
59047
|
+
if (machine.name !== normalizedName) {
|
|
59048
|
+
throw new MementosApiProtocolError("PATCH /v1/machines/:id", "returned name does not match the requested rename");
|
|
59049
|
+
}
|
|
59050
|
+
return machine;
|
|
59051
|
+
}
|
|
59052
|
+
const d = db ?? getDatabase();
|
|
59053
|
+
if (!getMachineById(id, d))
|
|
59054
|
+
throw new MachineRegistryError("MACHINE_NOT_FOUND", `Machine not found: ${id}`);
|
|
59055
|
+
try {
|
|
59056
|
+
const updated = machineFromDb(d.query("UPDATE machines SET name = ? WHERE id = ? RETURNING *").get(normalizedName, id));
|
|
59057
|
+
if (!updated)
|
|
59058
|
+
throw new MachineRegistryError("MACHINE_NOT_FOUND", `Machine not found: ${id}`);
|
|
59059
|
+
return updated;
|
|
59060
|
+
} catch (error) {
|
|
59061
|
+
if (error instanceof MachineRegistryError)
|
|
59062
|
+
throw error;
|
|
59063
|
+
const clash = d.query("SELECT id FROM machines WHERE name = ? AND id != ?").get(normalizedName, id);
|
|
59064
|
+
if (clash)
|
|
59065
|
+
throw new MachineRegistryError("MACHINE_NAME_CONFLICT", `Machine name already taken: ${normalizedName}`);
|
|
59066
|
+
throw error;
|
|
59067
|
+
}
|
|
59068
|
+
}
|
|
59069
|
+
function getPrimaryMachine(db) {
|
|
59070
|
+
if (!db && isApiMode())
|
|
59071
|
+
return listMachines().find((machine) => machine.is_primary) ?? null;
|
|
59072
|
+
const d = db ?? getDatabase();
|
|
59073
|
+
return machineFromDb(d.query("SELECT * FROM machines WHERE is_primary = 1 LIMIT 1").get());
|
|
59074
|
+
}
|
|
59075
|
+
function getPrimaryMachineCandidate(db) {
|
|
59076
|
+
if (!db && isApiMode()) {
|
|
59077
|
+
const machines = listMachines();
|
|
59078
|
+
if (machines.some((machine) => machine.is_primary))
|
|
59079
|
+
return null;
|
|
59080
|
+
return [...machines].sort((a, b) => a.created_at.localeCompare(b.created_at) || a.id.localeCompare(b.id))[0] ?? null;
|
|
59081
|
+
}
|
|
59082
|
+
const d = db ?? getDatabase();
|
|
59083
|
+
if (getPrimaryMachine(d))
|
|
57872
59084
|
return null;
|
|
57873
|
-
return
|
|
59085
|
+
return machineFromDb(d.query("SELECT * FROM machines ORDER BY created_at ASC, id ASC LIMIT 1").get());
|
|
57874
59086
|
}
|
|
57875
|
-
function setPrimaryMachine(id, db
|
|
57876
|
-
|
|
57877
|
-
|
|
57878
|
-
|
|
57879
|
-
|
|
57880
|
-
|
|
57881
|
-
|
|
57882
|
-
|
|
57883
|
-
}
|
|
57884
|
-
|
|
57885
|
-
|
|
57886
|
-
|
|
57887
|
-
|
|
57888
|
-
|
|
57889
|
-
|
|
59087
|
+
function setPrimaryMachine(id, db) {
|
|
59088
|
+
if (!db && isApiMode()) {
|
|
59089
|
+
const { data } = apiJson("POST", `/machines/${encodeURIComponent(id)}/primary`);
|
|
59090
|
+
const machine = decodeMachineMutation(data, "POST /v1/machines/:id/primary", id);
|
|
59091
|
+
if (!machine.is_primary) {
|
|
59092
|
+
throw new MementosApiProtocolError("POST /v1/machines/:id/primary", "returned machine is not primary");
|
|
59093
|
+
}
|
|
59094
|
+
return machine;
|
|
59095
|
+
}
|
|
59096
|
+
const d = db ?? getDatabase();
|
|
59097
|
+
return d.transaction(() => {
|
|
59098
|
+
if (!getMachineById(id, d))
|
|
59099
|
+
throw new MachineRegistryError("MACHINE_NOT_FOUND", `Machine not found: ${id}`);
|
|
59100
|
+
d.run("UPDATE machines SET is_primary = 0 WHERE is_primary = 1 AND id != ?", id);
|
|
59101
|
+
const updated = machineFromDb(d.query("UPDATE machines SET is_primary = 1 WHERE id = ? RETURNING *").get(id));
|
|
59102
|
+
if (!updated)
|
|
59103
|
+
throw new MachineRegistryError("MACHINE_NOT_FOUND", `Machine not found: ${id}`);
|
|
59104
|
+
return updated;
|
|
59105
|
+
});
|
|
59106
|
+
}
|
|
59107
|
+
function deleteMachine(id, db) {
|
|
59108
|
+
if (!db && isApiMode()) {
|
|
59109
|
+
const operation = "DELETE /v1/machines/:id";
|
|
59110
|
+
const { data } = apiJson("DELETE", `/machines/${encodeURIComponent(id)}`);
|
|
59111
|
+
const object2 = expectObject(data, operation);
|
|
59112
|
+
expectContract(object2, MACHINE_MUTATION_CONTRACT, operation);
|
|
59113
|
+
if (expectString(object2, "id", operation) !== id || object2["deleted"] !== true) {
|
|
59114
|
+
throw new MementosApiProtocolError(operation, "expected deleted=true for the requested stable id");
|
|
59115
|
+
}
|
|
59116
|
+
if (hostedMachineCache?.id === id)
|
|
59117
|
+
hostedMachineCache = null;
|
|
59118
|
+
return;
|
|
57890
59119
|
}
|
|
57891
|
-
|
|
59120
|
+
const d = db ?? getDatabase();
|
|
59121
|
+
const machine = getMachineById(id, d);
|
|
59122
|
+
if (!machine)
|
|
59123
|
+
throw new MachineRegistryError("MACHINE_NOT_FOUND", `Machine not found: ${id}`);
|
|
59124
|
+
if (machine.is_primary)
|
|
59125
|
+
throw new MachineRegistryError("MACHINE_PRIMARY_DELETE_REFUSED", `Primary machine cannot be deleted: ${machine.name}`);
|
|
59126
|
+
d.run("DELETE FROM machines WHERE id = ?", id);
|
|
57892
59127
|
}
|
|
57893
|
-
function getFallbackSyncTargetMachine(db
|
|
59128
|
+
function getFallbackSyncTargetMachine(db) {
|
|
57894
59129
|
return getPrimaryMachine(db);
|
|
57895
59130
|
}
|
|
57896
|
-
function getPrimaryMachineStartupWarning(db
|
|
59131
|
+
function getPrimaryMachineStartupWarning(db) {
|
|
57897
59132
|
if (getPrimaryMachine(db))
|
|
57898
59133
|
return null;
|
|
57899
59134
|
const candidate = getPrimaryMachineCandidate(db);
|
|
57900
|
-
if (!candidate)
|
|
59135
|
+
if (!candidate)
|
|
57901
59136
|
return "No primary machine configured. Fallback sync target is unset because no machines are registered yet.";
|
|
57902
|
-
}
|
|
57903
59137
|
return `No primary machine configured. Fallback sync target is unset. Candidate: ${candidate.name} (${candidate.id.slice(0, 8)} / ${candidate.hostname}). Confirm it with set_primary_machine.`;
|
|
57904
59138
|
}
|
|
57905
|
-
function touchMachine(id, db
|
|
57906
|
-
db
|
|
57907
|
-
|
|
57908
|
-
|
|
57909
|
-
|
|
57910
|
-
|
|
57911
|
-
|
|
57912
|
-
|
|
57913
|
-
|
|
59139
|
+
function touchMachine(id, db) {
|
|
59140
|
+
if (!db && isApiMode()) {
|
|
59141
|
+
const operation = "POST /v1/machines/:id/touch";
|
|
59142
|
+
const { data } = apiJson("POST", `/machines/${encodeURIComponent(id)}/touch`);
|
|
59143
|
+
const object2 = expectObject(data, operation);
|
|
59144
|
+
expectContract(object2, MACHINE_TOUCH_CONTRACT, operation);
|
|
59145
|
+
if (object2["touched"] !== true || expectString(object2, "id", operation) !== id) {
|
|
59146
|
+
throw new MementosApiProtocolError(operation, "expected touched=true for the requested stable id");
|
|
59147
|
+
}
|
|
59148
|
+
const machine2 = decodeMachine(object2["machine"], operation);
|
|
59149
|
+
const touchedAt2 = canonicalTimestamp(object2["touched_at"], operation, "touched_at");
|
|
59150
|
+
if (machine2.id !== id || machine2.last_seen_at !== touchedAt2) {
|
|
59151
|
+
throw new MementosApiProtocolError(operation, "touch receipt does not match the returned machine");
|
|
59152
|
+
}
|
|
59153
|
+
return machine2;
|
|
57914
59154
|
}
|
|
57915
|
-
|
|
59155
|
+
const d = db ?? getDatabase();
|
|
59156
|
+
const touchedAt = now();
|
|
59157
|
+
const machine = machineFromDb(d.query("UPDATE machines SET last_seen_at = ? WHERE id = ? RETURNING *").get(touchedAt, id));
|
|
59158
|
+
if (!machine || machine.id !== id)
|
|
59159
|
+
throw new MachineRegistryError("MACHINE_NOT_FOUND", `Machine not found: ${id}`);
|
|
59160
|
+
return machine;
|
|
59161
|
+
}
|
|
59162
|
+
var hostedMachineCache = null;
|
|
59163
|
+
function getCurrentMachineId(db) {
|
|
59164
|
+
if (!db && isApiMode()) {
|
|
59165
|
+
const host = normalizeMachineHostname(hostname());
|
|
59166
|
+
const plat = normalizeMachinePlatform(platform());
|
|
59167
|
+
const configBefore = getApiConfig();
|
|
59168
|
+
if (!configBefore)
|
|
59169
|
+
throw new Error("mementos hosted machine identity requires a resolved API authority");
|
|
59170
|
+
const credentialFingerprint = createHash5("sha256").update(configBefore.apiKey).digest("hex");
|
|
59171
|
+
const key = `${configBefore.baseUrl}\x00${credentialFingerprint}\x00${host}\x00${plat}`;
|
|
59172
|
+
if (hostedMachineCache?.key === key)
|
|
59173
|
+
return hostedMachineCache.id;
|
|
59174
|
+
const id = registerMachine().id;
|
|
59175
|
+
const configAfter = getApiConfig();
|
|
59176
|
+
const fingerprintAfter = configAfter ? createHash5("sha256").update(configAfter.apiKey).digest("hex") : null;
|
|
59177
|
+
if (!configAfter || configAfter.baseUrl !== configBefore.baseUrl || fingerprintAfter !== credentialFingerprint) {
|
|
59178
|
+
throw new Error("MEMENTOS_AUTHORITY_CHANGED: the machine registry authority or credential changed while resolving the stable machine identity");
|
|
59179
|
+
}
|
|
59180
|
+
hostedMachineCache = { key, id };
|
|
59181
|
+
return id;
|
|
59182
|
+
}
|
|
59183
|
+
const d = db ?? getDatabase();
|
|
59184
|
+
return registerMachine(undefined, d).id;
|
|
57916
59185
|
}
|
|
57917
59186
|
// src/lib/search.ts
|
|
57918
59187
|
init_database();
|
|
@@ -59177,14 +60446,27 @@ function ensureDir2(dir) {
|
|
|
59177
60446
|
mkdirSync3(dir, { recursive: true });
|
|
59178
60447
|
}
|
|
59179
60448
|
}
|
|
60449
|
+
// src/lib/injection-project.ts
|
|
60450
|
+
function resolveInjectionProjectId(projectRef, db) {
|
|
60451
|
+
if (projectRef === undefined)
|
|
60452
|
+
return;
|
|
60453
|
+
const project = getProject(projectRef, db);
|
|
60454
|
+
if (!project)
|
|
60455
|
+
throw new Error(`Project not found: ${projectRef}`);
|
|
60456
|
+
return project.id;
|
|
60457
|
+
}
|
|
60458
|
+
|
|
59180
60459
|
// src/lib/machine-visibility.ts
|
|
60460
|
+
init_api_mode();
|
|
59181
60461
|
function resolveVisibleMachineId(machineId, db) {
|
|
59182
60462
|
if (machineId !== undefined) {
|
|
59183
60463
|
return machineId;
|
|
59184
60464
|
}
|
|
59185
60465
|
try {
|
|
59186
60466
|
return getCurrentMachineId(db);
|
|
59187
|
-
} catch {
|
|
60467
|
+
} catch (error) {
|
|
60468
|
+
if (!db && isApiMode())
|
|
60469
|
+
throw error;
|
|
59188
60470
|
return null;
|
|
59189
60471
|
}
|
|
59190
60472
|
}
|
|
@@ -59194,6 +60476,10 @@ function visibleToMachineFilter(machineId, db) {
|
|
|
59194
60476
|
};
|
|
59195
60477
|
}
|
|
59196
60478
|
|
|
60479
|
+
// src/lib/profile-synthesizer.ts
|
|
60480
|
+
init_api_mode();
|
|
60481
|
+
init_storage();
|
|
60482
|
+
|
|
59197
60483
|
// src/db/tool-events.ts
|
|
59198
60484
|
init_database();
|
|
59199
60485
|
init_api_mode();
|
|
@@ -59206,6 +60492,10 @@ class MemoryInjector {
|
|
|
59206
60492
|
this.config = config || loadConfig();
|
|
59207
60493
|
}
|
|
59208
60494
|
getInjectionContext(options = {}) {
|
|
60495
|
+
const projectId = resolveInjectionProjectId(options.project_id, options.db);
|
|
60496
|
+
return this.getInjectionContextForProject(options, projectId);
|
|
60497
|
+
}
|
|
60498
|
+
getInjectionContextForProject(options, projectId) {
|
|
59209
60499
|
const maxTokens = options.max_tokens || this.config.injection.max_tokens;
|
|
59210
60500
|
const minImportance = options.min_importance || this.config.injection.min_importance;
|
|
59211
60501
|
const categories = options.categories || this.config.injection.categories;
|
|
@@ -59221,13 +60511,13 @@ class MemoryInjector {
|
|
|
59221
60511
|
limit: 100
|
|
59222
60512
|
}, db);
|
|
59223
60513
|
allMemories.push(...globalMems);
|
|
59224
|
-
if (
|
|
60514
|
+
if (projectId) {
|
|
59225
60515
|
const sharedMems = listMemories({
|
|
59226
60516
|
scope: "shared",
|
|
59227
60517
|
category: categories,
|
|
59228
60518
|
min_importance: minImportance,
|
|
59229
60519
|
status: "active",
|
|
59230
|
-
project_id:
|
|
60520
|
+
project_id: projectId,
|
|
59231
60521
|
...visibleToMachineFilter(visibleMachineId),
|
|
59232
60522
|
limit: 100
|
|
59233
60523
|
}, db);
|
|
@@ -59240,6 +60530,8 @@ class MemoryInjector {
|
|
|
59240
60530
|
min_importance: minImportance,
|
|
59241
60531
|
status: "active",
|
|
59242
60532
|
agent_id: options.agent_id,
|
|
60533
|
+
project_id: projectId,
|
|
60534
|
+
include_unassigned_project: true,
|
|
59243
60535
|
...visibleToMachineFilter(visibleMachineId),
|
|
59244
60536
|
limit: 100
|
|
59245
60537
|
}, db);
|
|
@@ -59251,7 +60543,7 @@ class MemoryInjector {
|
|
|
59251
60543
|
status: "active",
|
|
59252
60544
|
...options.session_id ? { session_id: options.session_id } : {},
|
|
59253
60545
|
...options.agent_id ? { agent_id: options.agent_id } : {},
|
|
59254
|
-
...
|
|
60546
|
+
...projectId ? { project_id: projectId } : {},
|
|
59255
60547
|
...visibleToMachineFilter(visibleMachineId),
|
|
59256
60548
|
limit: 100
|
|
59257
60549
|
}, db);
|
|
@@ -59356,6 +60648,7 @@ ${sections.join(`
|
|
|
59356
60648
|
if (!options.query) {
|
|
59357
60649
|
return this.getInjectionContext(options);
|
|
59358
60650
|
}
|
|
60651
|
+
const projectId = resolveInjectionProjectId(options.project_id, options.db);
|
|
59359
60652
|
const maxTokens = options.max_tokens || this.config.injection.max_tokens;
|
|
59360
60653
|
const minImportance = options.min_importance || this.config.injection.min_importance;
|
|
59361
60654
|
const categories = options.categories || this.config.injection.categories;
|
|
@@ -59371,13 +60664,13 @@ ${sections.join(`
|
|
|
59371
60664
|
limit: 100
|
|
59372
60665
|
}, db);
|
|
59373
60666
|
allMemories.push(...globalMems);
|
|
59374
|
-
if (
|
|
60667
|
+
if (projectId) {
|
|
59375
60668
|
const sharedMems = listMemories({
|
|
59376
60669
|
scope: "shared",
|
|
59377
60670
|
category: categories,
|
|
59378
60671
|
min_importance: minImportance,
|
|
59379
60672
|
status: "active",
|
|
59380
|
-
project_id:
|
|
60673
|
+
project_id: projectId,
|
|
59381
60674
|
...visibleToMachineFilter(visibleMachineId),
|
|
59382
60675
|
limit: 100
|
|
59383
60676
|
}, db);
|
|
@@ -59390,6 +60683,8 @@ ${sections.join(`
|
|
|
59390
60683
|
min_importance: minImportance,
|
|
59391
60684
|
status: "active",
|
|
59392
60685
|
agent_id: options.agent_id,
|
|
60686
|
+
project_id: projectId,
|
|
60687
|
+
include_unassigned_project: true,
|
|
59393
60688
|
...visibleToMachineFilter(visibleMachineId),
|
|
59394
60689
|
limit: 100
|
|
59395
60690
|
}, db);
|
|
@@ -59401,7 +60696,7 @@ ${sections.join(`
|
|
|
59401
60696
|
status: "active",
|
|
59402
60697
|
...options.session_id ? { session_id: options.session_id } : {},
|
|
59403
60698
|
...options.agent_id ? { agent_id: options.agent_id } : {},
|
|
59404
|
-
...
|
|
60699
|
+
...projectId ? { project_id: projectId } : {},
|
|
59405
60700
|
...visibleToMachineFilter(visibleMachineId),
|
|
59406
60701
|
limit: 100
|
|
59407
60702
|
}, db);
|
|
@@ -59427,7 +60722,7 @@ ${sections.join(`
|
|
|
59427
60722
|
} catch {}
|
|
59428
60723
|
}
|
|
59429
60724
|
if (embeddingMap.size === 0) {
|
|
59430
|
-
return this.
|
|
60725
|
+
return this.getInjectionContextForProject(options, projectId);
|
|
59431
60726
|
}
|
|
59432
60727
|
const nowMs = Date.now();
|
|
59433
60728
|
const oldestMs = Math.min(...unique.map((m) => new Date(m.updated_at).getTime()));
|
|
@@ -59685,6 +60980,14 @@ var defaultSyncAgents = ["claude", "codex", "gemini"];
|
|
|
59685
60980
|
// src/lib/storage-sync.ts
|
|
59686
60981
|
init_storage();
|
|
59687
60982
|
init_database();
|
|
60983
|
+
init_local_opt_in();
|
|
60984
|
+
var STORAGE_SYNC_LOCAL_ONLY_MESSAGE = "REMOTE_COMMAND_UNSUPPORTED: mementos storage push/pull/sync/status operate on the on-box SQLite store, " + "which is opt-in only (" + `${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]}=1 or an explicit ${MEMENTOS_DB_PATH_ENV_KEYS[0]}` + ") and disabled by default. On the hosted route there is no local store to sync \u2014 refusing rather than " + "creating one. Run these verbs where the on-box store deliberately lives (the local opt-in) or in mementos-serve.";
|
|
60985
|
+
function openLocalSyncAdapter() {
|
|
60986
|
+
if (!isServerContext() && !selectsMementosLocalStore(process.env)) {
|
|
60987
|
+
throw new Error(STORAGE_SYNC_LOCAL_ONLY_MESSAGE);
|
|
60988
|
+
}
|
|
60989
|
+
return new SqliteAdapter(getDbPath2());
|
|
60990
|
+
}
|
|
59688
60991
|
var MEMORY_TABLE = "memories";
|
|
59689
60992
|
var MEMORY_SYNC_META_TABLE = "_mementos_storage_sync_meta";
|
|
59690
60993
|
var SOURCE_MACHINE_METADATA_KEY = "last_synced_source_machine";
|
|
@@ -60063,7 +61366,7 @@ function resolveTables(local, tables) {
|
|
|
60063
61366
|
function withManagedAdapters(options, fn) {
|
|
60064
61367
|
const localOwned = !options.local;
|
|
60065
61368
|
const remoteOwned = !options.remote;
|
|
60066
|
-
const local = options.local ??
|
|
61369
|
+
const local = options.local ?? openLocalSyncAdapter();
|
|
60067
61370
|
const remote = options.remote ?? new PgAdapter(getStorageConnectionString("mementos"));
|
|
60068
61371
|
try {
|
|
60069
61372
|
const currentMachineId = resolveCurrentMachineId(local, options.current_machine_id);
|
|
@@ -60119,7 +61422,7 @@ function pullStorageChanges(options = {}) {
|
|
|
60119
61422
|
function getStorageSyncStatus(options = {}) {
|
|
60120
61423
|
const backend = getStorageBackend();
|
|
60121
61424
|
const localOwned = !options.local;
|
|
60122
|
-
const local = options.local ??
|
|
61425
|
+
const local = options.local ?? openLocalSyncAdapter();
|
|
60123
61426
|
try {
|
|
60124
61427
|
const currentMachineId = resolveCurrentMachineId(local, options.current_machine_id);
|
|
60125
61428
|
return {
|
|
@@ -62729,6 +64032,8 @@ export {
|
|
|
62729
64032
|
getMemoryLinks,
|
|
62730
64033
|
getMemoryByKey,
|
|
62731
64034
|
getMemoryBacklinks,
|
|
64035
|
+
getMemoryAuditTrailPage,
|
|
64036
|
+
getMemoryAuditTrail,
|
|
62732
64037
|
getMemory,
|
|
62733
64038
|
getMemoriesForEntity,
|
|
62734
64039
|
getMemoriesByKey,
|
|
@@ -62747,6 +64052,7 @@ export {
|
|
|
62747
64052
|
getDatabase,
|
|
62748
64053
|
getCurrentMachineId,
|
|
62749
64054
|
getAutoMemoryStats,
|
|
64055
|
+
getAuditStats,
|
|
62750
64056
|
getAgent,
|
|
62751
64057
|
getActiveProfile,
|
|
62752
64058
|
getActiveModel,
|
|
@@ -62754,6 +64060,8 @@ export {
|
|
|
62754
64060
|
formatMementosProjectPanel,
|
|
62755
64061
|
focusFilterSQL,
|
|
62756
64062
|
findPath,
|
|
64063
|
+
exportAuditLogPage,
|
|
64064
|
+
exportAuditLog,
|
|
62757
64065
|
enforceQuotas,
|
|
62758
64066
|
digestMementosProjectRegistrationValue,
|
|
62759
64067
|
deriveMementosProjectRegistrationIdempotencyKey,
|
|
@@ -62815,6 +64123,7 @@ export {
|
|
|
62815
64123
|
MementosProjectRegistrationHttpClient,
|
|
62816
64124
|
MementosProjectRegistrationError,
|
|
62817
64125
|
MementosProjectAuthorityIdentityError,
|
|
64126
|
+
MachineRegistryError,
|
|
62818
64127
|
MEMORY_PROJECT_LINK_RECEIPT_COLUMNS,
|
|
62819
64128
|
MEMENTOS_STORAGE_TABLES,
|
|
62820
64129
|
MEMENTOS_STORAGE_FALLBACK_ENV,
|
|
@@ -62828,11 +64137,19 @@ export {
|
|
|
62828
64137
|
MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
|
|
62829
64138
|
MEMENTOS_PROJECT_AUTHORITY_ENV,
|
|
62830
64139
|
MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
|
|
64140
|
+
MACHINE_TOUCH_CONTRACT,
|
|
64141
|
+
MACHINE_REGISTRATION_CONTRACT,
|
|
64142
|
+
MACHINE_MUTATION_CONTRACT,
|
|
64143
|
+
MACHINE_LIST_CONTRACT,
|
|
62831
64144
|
InvalidScopeError,
|
|
62832
64145
|
EntityNotFoundError,
|
|
62833
64146
|
DuplicateMemoryError,
|
|
62834
64147
|
DEFAULT_REFLECTION_PROVIDER,
|
|
62835
64148
|
DEFAULT_REFLECTION_MODEL,
|
|
62836
64149
|
DEFAULT_MODEL,
|
|
62837
|
-
DEFAULT_CONFIG
|
|
64150
|
+
DEFAULT_CONFIG,
|
|
64151
|
+
AUDIT_TRAIL_CONTRACT,
|
|
64152
|
+
AUDIT_STATS_CONTRACT,
|
|
64153
|
+
AUDIT_OPERATIONS,
|
|
64154
|
+
AUDIT_EXPORT_CONTRACT
|
|
62838
64155
|
};
|