@ouro.bot/cli 0.1.0-alpha.816 → 0.1.0-alpha.817
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/assets/sanctuary-host-launcher.sh +16 -0
- package/changelog.json +10 -0
- package/deploy/unraid/Dockerfile +6 -0
- package/deploy/unraid/README.txt +280 -60
- package/deploy/unraid/docker-man-template-transaction.mjs +192 -7
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +1 -1
- package/deploy/unraid/sanctuary-acceptance-contract.json +7 -7
- package/deploy/unraid/sanctuary-authority-installation.json +36 -0
- package/deploy/unraid/sanctuary-authority-service.sh +30 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +62 -9
- package/deploy/unraid/sanctuary-unit16-run.sh +15 -15
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +2 -2
- package/deploy/unraid/sanctuary.xml +2 -1
- package/dist/heart/core.js +2 -1
- package/dist/heart/daemon/container-spec-auditor-main.js +8 -7
- package/dist/heart/daemon/container-spec-auditor.js +16 -9
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +95 -91
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +95 -169
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +4 -5
- package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
- package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
- package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
- package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +780 -0
- package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
- package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
- package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
- package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
- package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
- package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +615 -0
- package/dist/heart/daemon/sense-manager.js +20 -10
- package/dist/repertoire/tools-sanctuary-host.js +202 -0
- package/dist/repertoire/tools.js +15 -6
- package/dist/senses/root-host-approval-port.js +345 -0
- package/dist/senses/root-host-approval-runtime.js +393 -0
- package/dist/senses/sanctuary-authority-resident.js +102 -0
- package/dist/senses/telegram-admission.js +7 -0
- package/dist/senses/telegram-attachments.js +5 -1
- package/dist/senses/telegram-authority-transport.js +231 -0
- package/dist/senses/telegram-client.js +31 -8
- package/dist/senses/telegram-entry.js +1 -1
- package/dist/senses/telegram.js +168 -28
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readSanctuaryAuthorityEpoch = readSanctuaryAuthorityEpoch;
|
|
37
|
+
exports.prepareSanctuaryAuthorityEpoch = prepareSanctuaryAuthorityEpoch;
|
|
38
|
+
exports.releaseSanctuaryAuthorityToken = releaseSanctuaryAuthorityToken;
|
|
39
|
+
exports.retireSanctuaryAuthorityEpoch = retireSanctuaryAuthorityEpoch;
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const session_transaction_1 = require("../../mind/session-transaction");
|
|
44
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
45
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
46
|
+
const TOKEN = /^[1-9][0-9]*:[A-Za-z0-9_-]{20,}$/u;
|
|
47
|
+
const sha = (bytes) => `sha256:${(0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
48
|
+
function assertRoot(root, owner) {
|
|
49
|
+
const stat = fs.lstatSync(root);
|
|
50
|
+
if (!stat.isDirectory() || fs.realpathSync(root) !== root || stat.uid !== owner.expectedUid || stat.gid !== owner.expectedGid || (stat.mode & 0o7777) !== 0o700)
|
|
51
|
+
throw new Error("Sanctuary authority epoch root is unsafe");
|
|
52
|
+
}
|
|
53
|
+
function privateBytes(filePath, owner) {
|
|
54
|
+
if (!path.isAbsolute(filePath) || fs.realpathSync(filePath) !== filePath)
|
|
55
|
+
throw new Error("Sanctuary authority epoch path is unsafe");
|
|
56
|
+
const fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
57
|
+
try {
|
|
58
|
+
const stat = fs.fstatSync(fd);
|
|
59
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== owner.expectedUid || stat.gid !== owner.expectedGid || (stat.mode & 0o7777) !== 0o600 || stat.size < 1 || stat.size > 65536)
|
|
60
|
+
throw new Error("Sanctuary authority epoch file metadata is unsafe");
|
|
61
|
+
return fs.readFileSync(fd);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
fs.closeSync(fd);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function issuer(root, owner) {
|
|
68
|
+
const key = (0, node_crypto_1.createPrivateKey)(privateBytes(path.join(root, "issuer.pem"), owner));
|
|
69
|
+
const publicKey = (0, node_crypto_1.createPublicKey)(key);
|
|
70
|
+
if (publicKey.asymmetricKeyType !== "ed25519")
|
|
71
|
+
throw new Error("Sanctuary authority issuer is not Ed25519");
|
|
72
|
+
return { publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(), publicKeyDigest: sha(publicKey.export({ type: "spki", format: "der" })) };
|
|
73
|
+
}
|
|
74
|
+
function validate(value) {
|
|
75
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
76
|
+
throw new Error("Sanctuary authority epoch is invalid");
|
|
77
|
+
const epoch = value;
|
|
78
|
+
if (Object.keys(epoch).sort().join(",") !== "botId,createdAt,epochId,ownerChatId,ownerUserId,packageDigest,predecessorCursor,previousTokenDigest,publicKeyDigest,publicKeyPem,revokedTokenStatus,schemaVersion,state,terminalCursor,tokenDigest,tokenPath"
|
|
79
|
+
|| epoch.schemaVersion !== 1 || !["prepared", "retired"].includes(epoch.state)
|
|
80
|
+
|| typeof epoch.epochId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/u.test(epoch.epochId)
|
|
81
|
+
|| ![epoch.botId, epoch.ownerUserId, epoch.ownerChatId].every((id) => typeof id === "string" && /^[1-9][0-9]*$/u.test(id)) || epoch.ownerUserId !== epoch.ownerChatId
|
|
82
|
+
|| !Number.isSafeInteger(epoch.predecessorCursor) || epoch.predecessorCursor < 0
|
|
83
|
+
|| ![epoch.packageDigest, epoch.tokenDigest, epoch.previousTokenDigest, epoch.publicKeyDigest].every((value) => typeof value === "string" && DIGEST.test(value))
|
|
84
|
+
|| epoch.tokenDigest === epoch.previousTokenDigest || typeof epoch.tokenPath !== "string" || !path.isAbsolute(epoch.tokenPath)
|
|
85
|
+
|| typeof epoch.publicKeyPem !== "string" || epoch.revokedTokenStatus !== 401
|
|
86
|
+
|| typeof epoch.createdAt !== "string" || !Number.isFinite(Date.parse(epoch.createdAt)) || new Date(epoch.createdAt).toISOString() !== epoch.createdAt
|
|
87
|
+
|| (epoch.state === "prepared" ? epoch.terminalCursor !== null : !Number.isSafeInteger(epoch.terminalCursor) || epoch.terminalCursor < epoch.predecessorCursor))
|
|
88
|
+
throw new Error("Sanctuary authority epoch is invalid");
|
|
89
|
+
return epoch;
|
|
90
|
+
}
|
|
91
|
+
function syncRoot(root) {
|
|
92
|
+
const fd = fs.openSync(root, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY);
|
|
93
|
+
try {
|
|
94
|
+
fs.fsyncSync(fd);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
fs.closeSync(fd);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function writeRecord(filePath, record) {
|
|
101
|
+
(0, session_transaction_1.withImmediateSessionTurnLease)(filePath, (lease) => {
|
|
102
|
+
const transaction = (0, session_transaction_1.readSessionTransaction)(filePath, lease);
|
|
103
|
+
(0, session_transaction_1.writeSessionTransaction)(filePath, record, { lease, expectedRevision: transaction.revision });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function writeIssuer(root, key, owner) {
|
|
107
|
+
const temporary = path.join(root, "issuer.pem.tmp");
|
|
108
|
+
const fd = fs.openSync(temporary, fs.constants.O_CREAT | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW, 0o600);
|
|
109
|
+
let owned = false;
|
|
110
|
+
try {
|
|
111
|
+
try {
|
|
112
|
+
const stat = fs.fstatSync(fd);
|
|
113
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== owner.expectedUid || stat.gid !== owner.expectedGid || (stat.mode & 0o7777) !== 0o600)
|
|
114
|
+
throw new Error("Sanctuary issuer temporary is unsafe");
|
|
115
|
+
owned = true;
|
|
116
|
+
fs.ftruncateSync(fd, 0);
|
|
117
|
+
fs.writeFileSync(fd, key);
|
|
118
|
+
fs.fsyncSync(fd);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
fs.closeSync(fd);
|
|
122
|
+
}
|
|
123
|
+
fs.renameSync(temporary, path.join(root, "issuer.pem"));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (owned)
|
|
127
|
+
fs.unlinkSync(temporary);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
syncRoot(root);
|
|
131
|
+
}
|
|
132
|
+
function readSanctuaryAuthorityEpoch(root, owner) {
|
|
133
|
+
assertRoot(root, owner);
|
|
134
|
+
const epoch = validate(JSON.parse(privateBytes(path.join(root, "epoch.json"), owner).toString("utf8")));
|
|
135
|
+
const publicKey = issuer(root, owner);
|
|
136
|
+
if (publicKey.publicKeyDigest !== epoch.publicKeyDigest || publicKey.publicKeyPem !== epoch.publicKeyPem)
|
|
137
|
+
throw new Error("Sanctuary authority epoch key or token changed");
|
|
138
|
+
if (epoch.state === "retired" && fs.existsSync(path.join(root, "handoff.json"))) {
|
|
139
|
+
const handoff = JSON.parse(privateBytes(path.join(root, "handoff.json"), owner).toString("utf8"));
|
|
140
|
+
if (JSON.stringify(handoff) !== JSON.stringify({ schemaVersion: 1, epochId: epoch.epochId, tokenDigest: epoch.tokenDigest, cursor: epoch.terminalCursor }))
|
|
141
|
+
throw new Error("Sanctuary authority token handoff changed");
|
|
142
|
+
if (fs.existsSync(epoch.tokenPath) && sha(privateBytes(epoch.tokenPath, owner).toString("utf8").trim()) !== epoch.tokenDigest)
|
|
143
|
+
throw new Error("Sanctuary authority epoch key or token changed");
|
|
144
|
+
}
|
|
145
|
+
else if (sha(privateBytes(epoch.tokenPath, owner).toString("utf8").trim()) !== epoch.tokenDigest)
|
|
146
|
+
throw new Error("Sanctuary authority epoch key or token changed");
|
|
147
|
+
return epoch;
|
|
148
|
+
}
|
|
149
|
+
async function prepareSanctuaryAuthorityEpoch(input, options) {
|
|
150
|
+
assertRoot(input.root, options);
|
|
151
|
+
if (typeof input.epochId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/u.test(input.epochId)
|
|
152
|
+
|| ![input.botId, input.ownerUserId, input.ownerChatId].every((id) => typeof id === "string" && /^[1-9][0-9]*$/u.test(id)) || input.ownerUserId !== input.ownerChatId
|
|
153
|
+
|| !Number.isSafeInteger(input.predecessorCursor) || input.predecessorCursor < 0 || !DIGEST.test(input.packageDigest))
|
|
154
|
+
throw new Error("Sanctuary authority epoch input is invalid");
|
|
155
|
+
const token = privateBytes(input.tokenPath, options).toString("utf8").trim();
|
|
156
|
+
const previousToken = privateBytes(input.previousTokenPath, options).toString("utf8").trim();
|
|
157
|
+
if (!TOKEN.test(token) || !TOKEN.test(previousToken) || token.split(":")[0] !== input.botId || previousToken.split(":")[0] !== input.botId || token === previousToken)
|
|
158
|
+
throw new Error("Sanctuary authority requires a fresh same-bot token");
|
|
159
|
+
const previous = await options.probe(previousToken);
|
|
160
|
+
if (previous.status !== 401 || previous.botId !== null)
|
|
161
|
+
throw new Error("Sanctuary previous Telegram token is not proven revoked");
|
|
162
|
+
const current = await options.probe(token);
|
|
163
|
+
if (current.status !== 200 || current.botId !== input.botId)
|
|
164
|
+
throw new Error("Sanctuary new Telegram token identity is invalid");
|
|
165
|
+
const epochPath = path.join(input.root, "epoch.json");
|
|
166
|
+
const keyPath = path.join(input.root, "issuer.pem");
|
|
167
|
+
const intentPath = path.join(input.root, "issuer-intent.json");
|
|
168
|
+
const inputDigest = sha(JSON.stringify({ ...input, tokenDigest: sha(token), previousTokenDigest: sha(previousToken) }));
|
|
169
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(epochPath, (lease) => {
|
|
170
|
+
const transaction = (0, session_transaction_1.readSessionTransaction)(epochPath, lease);
|
|
171
|
+
let existing = false;
|
|
172
|
+
try {
|
|
173
|
+
fs.lstatSync(epochPath);
|
|
174
|
+
existing = true;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (error.code !== "ENOENT")
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
if (existing) {
|
|
181
|
+
const epoch = readSanctuaryAuthorityEpoch(input.root, options);
|
|
182
|
+
if (epoch.state === "retired")
|
|
183
|
+
throw new Error("Sanctuary authority epoch is retired; a fresh token and key epoch are required");
|
|
184
|
+
for (const key of ["epochId", "botId", "ownerUserId", "ownerChatId", "predecessorCursor", "packageDigest", "tokenPath"]) {
|
|
185
|
+
if (epoch[key] !== input[key])
|
|
186
|
+
throw new Error("Sanctuary authority epoch input changed");
|
|
187
|
+
}
|
|
188
|
+
if (epoch.previousTokenDigest !== sha(previousToken))
|
|
189
|
+
throw new Error("Sanctuary authority predecessor token changed");
|
|
190
|
+
if (fs.existsSync(intentPath)) {
|
|
191
|
+
const intent = JSON.parse(privateBytes(intentPath, options).toString("utf8"));
|
|
192
|
+
if (intent.inputDigest !== inputDigest || intent.key !== privateBytes(keyPath, options).toString("utf8"))
|
|
193
|
+
throw new Error("Sanctuary issuer cleanup identity changed");
|
|
194
|
+
fs.unlinkSync(intentPath);
|
|
195
|
+
syncRoot(input.root);
|
|
196
|
+
}
|
|
197
|
+
return epoch;
|
|
198
|
+
}
|
|
199
|
+
let intent;
|
|
200
|
+
if (fs.existsSync(intentPath)) {
|
|
201
|
+
intent = JSON.parse(privateBytes(intentPath, options).toString("utf8"));
|
|
202
|
+
if (!intent || Object.keys(intent).sort().join(",") !== "createdAt,inputDigest,key" || intent.inputDigest !== inputDigest || typeof intent.key !== "string")
|
|
203
|
+
throw new Error("Sanctuary issuer preparation identity changed");
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
if (fs.existsSync(keyPath))
|
|
207
|
+
throw new Error("Sanctuary authority issuer is orphaned; a fresh epoch is required");
|
|
208
|
+
intent = { inputDigest, key: (0, node_crypto_1.generateKeyPairSync)("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }).toString(), createdAt: options.now() };
|
|
209
|
+
writeRecord(intentPath, intent);
|
|
210
|
+
}
|
|
211
|
+
if (fs.existsSync(keyPath)) {
|
|
212
|
+
if (privateBytes(keyPath, options).toString("utf8") !== intent.key)
|
|
213
|
+
throw new Error("Sanctuary prepared issuer changed");
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
writeIssuer(input.root, intent.key, options);
|
|
217
|
+
}
|
|
218
|
+
const epoch = validate({
|
|
219
|
+
schemaVersion: 1, state: "prepared", epochId: input.epochId, botId: input.botId, ownerUserId: input.ownerUserId, ownerChatId: input.ownerChatId,
|
|
220
|
+
predecessorCursor: input.predecessorCursor, packageDigest: input.packageDigest, tokenPath: input.tokenPath, tokenDigest: sha(token), previousTokenDigest: sha(previousToken),
|
|
221
|
+
...issuer(input.root, options), revokedTokenStatus: 401, createdAt: intent.createdAt, terminalCursor: null,
|
|
222
|
+
});
|
|
223
|
+
(0, session_transaction_1.writeSessionTransaction)(epochPath, epoch, { lease, expectedRevision: transaction.revision });
|
|
224
|
+
fs.unlinkSync(intentPath);
|
|
225
|
+
syncRoot(input.root);
|
|
226
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_authority_epoch_prepared", message: "Sanctuary authority epoch prepared", meta: { epochId: epoch.epochId, publicKeyDigest: epoch.publicKeyDigest } });
|
|
227
|
+
return epoch;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function releaseSanctuaryAuthorityToken(root, options) {
|
|
231
|
+
const epoch = readSanctuaryAuthorityEpoch(root, options);
|
|
232
|
+
if (epoch.state !== "retired")
|
|
233
|
+
throw new Error("Sanctuary authority token cannot leave an unretired epoch");
|
|
234
|
+
if (epoch.tokenDigest !== options.tokenDigest || epoch.terminalCursor !== options.cursor || path.dirname(epoch.tokenPath) !== root)
|
|
235
|
+
throw new Error("Sanctuary authority token handoff is invalid");
|
|
236
|
+
writeRecord(path.join(root, "handoff.json"), { schemaVersion: 1, epochId: epoch.epochId, tokenDigest: epoch.tokenDigest, cursor: epoch.terminalCursor });
|
|
237
|
+
if (fs.existsSync(epoch.tokenPath))
|
|
238
|
+
fs.unlinkSync(epoch.tokenPath);
|
|
239
|
+
syncRoot(root);
|
|
240
|
+
}
|
|
241
|
+
function retireSanctuaryAuthorityEpoch(root, options) {
|
|
242
|
+
if (options.quiescent !== true)
|
|
243
|
+
throw new Error("Sanctuary authority execution state is not quiescent");
|
|
244
|
+
const epochPath = path.join(root, "epoch.json");
|
|
245
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(epochPath, (lease) => {
|
|
246
|
+
const transaction = (0, session_transaction_1.readSessionTransaction)(epochPath, lease);
|
|
247
|
+
const epoch = readSanctuaryAuthorityEpoch(root, options);
|
|
248
|
+
if (!Number.isSafeInteger(options.cursor) || options.cursor < epoch.predecessorCursor || (epoch.state === "retired" && options.cursor !== epoch.terminalCursor))
|
|
249
|
+
throw new Error("Sanctuary authority terminal cursor is invalid");
|
|
250
|
+
if (epoch.state === "retired")
|
|
251
|
+
return epoch;
|
|
252
|
+
const retired = { ...epoch, state: "retired", terminalCursor: options.cursor };
|
|
253
|
+
(0, session_transaction_1.writeSessionTransaction)(epochPath, retired, { lease, expectedRevision: transaction.revision });
|
|
254
|
+
return retired;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.verifySanctuaryAuthorityInstallation = verifySanctuaryAuthorityInstallation;
|
|
37
|
+
const node_crypto_1 = require("node:crypto");
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
+
function object(value) {
|
|
42
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
function digest(bytes) {
|
|
45
|
+
return `sha256:${(0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
46
|
+
}
|
|
47
|
+
function canonical(filePath) {
|
|
48
|
+
if (!path.isAbsolute(filePath) || path.normalize(filePath) !== filePath || fs.realpathSync(filePath) !== filePath) {
|
|
49
|
+
throw new Error("Sanctuary authority installation path is not canonical");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function directory(filePath, uid, gid, mode) {
|
|
53
|
+
canonical(filePath);
|
|
54
|
+
const stat = fs.lstatSync(filePath);
|
|
55
|
+
if (!stat.isDirectory() || stat.uid !== uid || stat.gid !== gid || (stat.mode & 0o7777) !== mode) {
|
|
56
|
+
throw new Error("Sanctuary authority installation directory metadata changed");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function readOwned(filePath, uid, gid, mode) {
|
|
60
|
+
canonical(filePath);
|
|
61
|
+
const descriptor = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
62
|
+
try {
|
|
63
|
+
const stat = fs.fstatSync(descriptor);
|
|
64
|
+
if (!stat.isFile() || stat.uid !== uid || stat.gid !== gid || (stat.mode & 0o7777) !== mode || stat.nlink !== 1) {
|
|
65
|
+
throw new Error("Sanctuary authority installation file metadata changed");
|
|
66
|
+
}
|
|
67
|
+
return fs.readFileSync(descriptor);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
fs.closeSync(descriptor);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function verifySanctuaryAuthorityInstallation(input) {
|
|
74
|
+
const { expectedUid: uid, expectedGid: gid } = input;
|
|
75
|
+
for (const root of [input.packageRoot, input.stateRoot, input.stagingRoot, input.cgroupRoot])
|
|
76
|
+
directory(root, uid, gid, 0o700);
|
|
77
|
+
directory(input.socketRoot, uid, input.socketGroupId, 0o750);
|
|
78
|
+
const bytes = readOwned(input.manifestPath, uid, gid, 0o600);
|
|
79
|
+
if (digest(bytes) !== input.manifestDigest)
|
|
80
|
+
throw new Error("Sanctuary authority package manifest digest changed");
|
|
81
|
+
const manifest = JSON.parse(bytes.toString("utf8"));
|
|
82
|
+
if (!object(manifest) || Object.keys(manifest).sort().join(",") !== "files,schemaVersion" || manifest.schemaVersion !== 1 || !object(manifest.files) || Object.keys(manifest.files).length === 0) {
|
|
83
|
+
throw new Error("Sanctuary authority package manifest is invalid");
|
|
84
|
+
}
|
|
85
|
+
const expected = Object.keys(manifest.files).sort();
|
|
86
|
+
for (const relative of expected) {
|
|
87
|
+
const pin = manifest.files[relative];
|
|
88
|
+
if (!/^[A-Za-z0-9_@.-]+(?:\/[A-Za-z0-9_@.-]+)*$/u.test(relative) || relative.split("/").some((part) => part === "." || part === "..")
|
|
89
|
+
|| !object(pin) || Object.keys(pin).sort().join(",") !== "digest,mode"
|
|
90
|
+
|| typeof pin.digest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(pin.digest) || ![0o600, 0o644, 0o700, 0o755].includes(pin.mode)) {
|
|
91
|
+
throw new Error("Sanctuary authority package file pin is invalid");
|
|
92
|
+
}
|
|
93
|
+
if (digest(readOwned(path.join(input.packageRoot, relative), uid, gid, pin.mode)) !== pin.digest)
|
|
94
|
+
throw new Error("Sanctuary authority package file digest changed");
|
|
95
|
+
}
|
|
96
|
+
const actual = [];
|
|
97
|
+
const walk = (relative) => {
|
|
98
|
+
for (const entry of fs.readdirSync(path.join(input.packageRoot, relative), { withFileTypes: true })) {
|
|
99
|
+
const name = path.join(relative, entry.name);
|
|
100
|
+
const filePath = path.join(input.packageRoot, name);
|
|
101
|
+
if (entry.isDirectory()) {
|
|
102
|
+
const stat = fs.lstatSync(filePath);
|
|
103
|
+
canonical(filePath);
|
|
104
|
+
if (stat.uid !== uid || stat.gid !== gid || ![0o700, 0o755].includes(stat.mode & 0o7777))
|
|
105
|
+
throw new Error("Sanctuary authority package directory is unsafe");
|
|
106
|
+
walk(name);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
actual.push(name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
walk("");
|
|
114
|
+
if (JSON.stringify(actual.sort()) !== JSON.stringify(expected))
|
|
115
|
+
throw new Error("Sanctuary authority package inventory changed");
|
|
116
|
+
const controllers = ["cpu", "memory", "pids"];
|
|
117
|
+
const readControl = (name) => fs.readFileSync(path.join(input.cgroupRoot, name), "utf8").trim();
|
|
118
|
+
for (const name of ["cgroup.controllers", "cgroup.subtree_control"]) {
|
|
119
|
+
const enabled = readControl(name).split(/\s+/u);
|
|
120
|
+
if (!controllers.every((controller) => enabled.includes(controller)))
|
|
121
|
+
throw new Error("Sanctuary authority cgroup controllers are unavailable");
|
|
122
|
+
}
|
|
123
|
+
if (readControl("cgroup.type") !== "domain" || readControl("cgroup.procs") !== "")
|
|
124
|
+
throw new Error("Sanctuary authority cgroup domain is invalid");
|
|
125
|
+
const mounts = (input.mountInfo ?? fs.readFileSync("/proc/self/mountinfo", "utf8")).trim().split("\n").map((line) => {
|
|
126
|
+
const fields = line.split(" ");
|
|
127
|
+
const separator = fields.indexOf("-");
|
|
128
|
+
if (fields.length < 10 || separator < 6)
|
|
129
|
+
throw new Error("Sanctuary authority mount inventory is invalid");
|
|
130
|
+
return { target: fields[4].replace(/\\([0-7]{3})/gu, (_match, octal) => String.fromCharCode(parseInt(octal, 8))), options: fields[5].split(","), type: fields[separator + 1] };
|
|
131
|
+
});
|
|
132
|
+
const mountFor = (filePath) => mounts.filter(({ target }) => target === "/" || filePath === target || filePath.startsWith(`${target}/`)).sort((a, b) => b.target.length - a.target.length)[0];
|
|
133
|
+
const stagingMount = mountFor(input.stagingRoot);
|
|
134
|
+
const cgroupMount = mountFor(input.cgroupRoot);
|
|
135
|
+
if (!stagingMount || stagingMount.options.includes("noexec") || stagingMount.options.includes("ro") || cgroupMount?.type !== "cgroup2" || cgroupMount.options.includes("ro")) {
|
|
136
|
+
throw new Error("Sanctuary authority staging or cgroup mount prerequisites are unavailable");
|
|
137
|
+
}
|
|
138
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_authority_installation_verified", message: "Sanctuary authority installation prerequisites verified", meta: { packageDigest: input.manifestDigest } });
|
|
139
|
+
return { packageDigest: input.manifestDigest, controllers };
|
|
140
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FileSanctuaryAuthorityLedger = void 0;
|
|
37
|
+
exports.sanctuaryAuthorityLedgerPath = sanctuaryAuthorityLedgerPath;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
+
const session_transaction_1 = require("../../mind/session-transaction");
|
|
42
|
+
const SCHEMA_VERSION = 1;
|
|
43
|
+
const TERMINAL_STATES = new Set(["verified", "failed", "ambiguous", "refused"]);
|
|
44
|
+
const PERMIT_ID = /^permit-[A-Za-z0-9_-]+$/;
|
|
45
|
+
const NONCE = /^[A-Za-z0-9_-]{64}$/;
|
|
46
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
47
|
+
function sanctuaryAuthorityLedgerPath(agentRoot) {
|
|
48
|
+
return path.join(agentRoot, "authority", "replay-ledger.json");
|
|
49
|
+
}
|
|
50
|
+
function isObject(value) {
|
|
51
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52
|
+
}
|
|
53
|
+
function hasExactKeys(value, keys) {
|
|
54
|
+
const actual = Object.keys(value).sort();
|
|
55
|
+
const expected = [...keys].sort();
|
|
56
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
57
|
+
}
|
|
58
|
+
function validTime(value) {
|
|
59
|
+
if (typeof value !== "string")
|
|
60
|
+
return false;
|
|
61
|
+
try {
|
|
62
|
+
return new Date(value).toISOString() === value;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function assertPermitId(value) {
|
|
69
|
+
if (typeof value !== "string" || !PERMIT_ID.test(value)) {
|
|
70
|
+
throw new Error("Sanctuary authority permit id is invalid");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function assertNonce(value) {
|
|
74
|
+
if (typeof value !== "string" || !NONCE.test(value)) {
|
|
75
|
+
throw new Error("Sanctuary authority nonce is invalid");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function assertDigest(value) {
|
|
79
|
+
if (typeof value !== "string" || !DIGEST.test(value)) {
|
|
80
|
+
throw new Error("Sanctuary authority digest is invalid");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function assertTime(value) {
|
|
84
|
+
if (!validTime(value)) {
|
|
85
|
+
throw new Error("Sanctuary authority time is invalid");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function validateReservation(value) {
|
|
89
|
+
if (!isObject(value) || !hasExactKeys(value, ["permitId", "nonce", "permitDigest", "reservedAt"])) {
|
|
90
|
+
throw new Error("Sanctuary authority ledger reservation is malformed");
|
|
91
|
+
}
|
|
92
|
+
assertPermitId(value.permitId);
|
|
93
|
+
assertNonce(value.nonce);
|
|
94
|
+
assertDigest(value.permitDigest);
|
|
95
|
+
assertTime(value.reservedAt);
|
|
96
|
+
}
|
|
97
|
+
function validateRecord(value, permitId) {
|
|
98
|
+
if (!isObject(value) || !hasExactKeys(value, [
|
|
99
|
+
"schemaVersion",
|
|
100
|
+
"permitId",
|
|
101
|
+
"nonce",
|
|
102
|
+
"permitDigest",
|
|
103
|
+
"reservedAt",
|
|
104
|
+
"state",
|
|
105
|
+
"updatedAt",
|
|
106
|
+
"outcomeDigest",
|
|
107
|
+
])) {
|
|
108
|
+
throw new Error(`Sanctuary authority ledger record ${permitId} is malformed`);
|
|
109
|
+
}
|
|
110
|
+
if (value.schemaVersion !== SCHEMA_VERSION || value.permitId !== permitId) {
|
|
111
|
+
throw new Error(`Sanctuary authority ledger record ${permitId} has invalid identity`);
|
|
112
|
+
}
|
|
113
|
+
assertPermitId(value.permitId);
|
|
114
|
+
assertNonce(value.nonce);
|
|
115
|
+
assertDigest(value.permitDigest);
|
|
116
|
+
assertTime(value.reservedAt);
|
|
117
|
+
assertTime(value.updatedAt);
|
|
118
|
+
if (value.state === "reserved") {
|
|
119
|
+
if (value.outcomeDigest !== null || value.updatedAt !== value.reservedAt) {
|
|
120
|
+
throw new Error(`Sanctuary authority ledger record ${permitId} has ambiguous reserved state`);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!TERMINAL_STATES.has(String(value.state))) {
|
|
125
|
+
throw new Error(`Sanctuary authority ledger record ${permitId} has invalid state`);
|
|
126
|
+
}
|
|
127
|
+
assertDigest(value.outcomeDigest);
|
|
128
|
+
}
|
|
129
|
+
function validateState(value) {
|
|
130
|
+
if (!isObject(value)
|
|
131
|
+
|| !hasExactKeys(value, ["schemaVersion", "records", "nonces"])
|
|
132
|
+
|| value.schemaVersion !== SCHEMA_VERSION
|
|
133
|
+
|| !isObject(value.records)
|
|
134
|
+
|| !isObject(value.nonces)) {
|
|
135
|
+
throw new Error("Sanctuary authority ledger state is malformed");
|
|
136
|
+
}
|
|
137
|
+
const records = {};
|
|
138
|
+
for (const [permitId, record] of Object.entries(value.records)) {
|
|
139
|
+
validateRecord(record, permitId);
|
|
140
|
+
records[permitId] = record;
|
|
141
|
+
if (value.nonces[record.nonce] !== permitId) {
|
|
142
|
+
throw new Error(`Sanctuary authority ledger nonce index is ambiguous for ${permitId}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const [nonce, permitId] of Object.entries(value.nonces)) {
|
|
146
|
+
assertNonce(nonce);
|
|
147
|
+
if (typeof permitId !== "string" || records[permitId]?.nonce !== nonce) {
|
|
148
|
+
throw new Error(`Sanctuary authority ledger nonce index is ambiguous for ${nonce}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function initialState() {
|
|
153
|
+
return { schemaVersion: SCHEMA_VERSION, records: {}, nonces: {} };
|
|
154
|
+
}
|
|
155
|
+
function transactionState(transaction) {
|
|
156
|
+
if (transaction.value === null) {
|
|
157
|
+
if (transaction.bytes)
|
|
158
|
+
throw new Error("Sanctuary authority ledger state is malformed");
|
|
159
|
+
return initialState();
|
|
160
|
+
}
|
|
161
|
+
validateState(transaction.value);
|
|
162
|
+
return transaction.value;
|
|
163
|
+
}
|
|
164
|
+
class FileSanctuaryAuthorityLedger {
|
|
165
|
+
#ledgerPath;
|
|
166
|
+
constructor(agentRoot) {
|
|
167
|
+
if (typeof agentRoot !== "string" || agentRoot.length === 0) {
|
|
168
|
+
throw new Error("Sanctuary authority ledger root is invalid");
|
|
169
|
+
}
|
|
170
|
+
this.#ledgerPath = sanctuaryAuthorityLedgerPath(agentRoot);
|
|
171
|
+
}
|
|
172
|
+
reserve(reservation) {
|
|
173
|
+
validateReservation(reservation);
|
|
174
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(this.#ledgerPath, (lease) => {
|
|
175
|
+
const transaction = (0, session_transaction_1.readSessionTransaction)(this.#ledgerPath, lease);
|
|
176
|
+
const state = transactionState(transaction);
|
|
177
|
+
if (state.records[reservation.permitId]) {
|
|
178
|
+
throw new Error(`Sanctuary authority permit id ${reservation.permitId} already exists`);
|
|
179
|
+
}
|
|
180
|
+
if (state.nonces[reservation.nonce]) {
|
|
181
|
+
throw new Error(`Sanctuary authority nonce ${reservation.nonce} already exists`);
|
|
182
|
+
}
|
|
183
|
+
const record = {
|
|
184
|
+
schemaVersion: SCHEMA_VERSION,
|
|
185
|
+
...reservation,
|
|
186
|
+
state: "reserved",
|
|
187
|
+
updatedAt: reservation.reservedAt,
|
|
188
|
+
outcomeDigest: null,
|
|
189
|
+
};
|
|
190
|
+
state.records[record.permitId] = record;
|
|
191
|
+
state.nonces[record.nonce] = record.permitId;
|
|
192
|
+
this.#write(state, transaction.revision, lease);
|
|
193
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_authority_permit_reserved", message: "Sanctuary host permit durably reserved", meta: { permitDigest: record.permitDigest } });
|
|
194
|
+
return record;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
read(permitId) {
|
|
198
|
+
assertPermitId(permitId);
|
|
199
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(this.#ledgerPath, (lease) => {
|
|
200
|
+
const transaction = (0, session_transaction_1.readSessionTransaction)(this.#ledgerPath, lease);
|
|
201
|
+
const state = transactionState(transaction);
|
|
202
|
+
return state.records[permitId] ?? null;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
terminalize(transition) {
|
|
206
|
+
if (!isObject(transition) || !hasExactKeys(transition, ["permitId", "state", "outcomeDigest", "updatedAt"])) {
|
|
207
|
+
throw new Error("Sanctuary authority ledger terminal transition is malformed");
|
|
208
|
+
}
|
|
209
|
+
assertPermitId(transition.permitId);
|
|
210
|
+
if (!TERMINAL_STATES.has(String(transition.state))) {
|
|
211
|
+
throw new Error("Sanctuary authority terminal state is invalid");
|
|
212
|
+
}
|
|
213
|
+
assertDigest(transition.outcomeDigest);
|
|
214
|
+
assertTime(transition.updatedAt);
|
|
215
|
+
return (0, session_transaction_1.withImmediateSessionTurnLease)(this.#ledgerPath, (lease) => {
|
|
216
|
+
const transaction = (0, session_transaction_1.readSessionTransaction)(this.#ledgerPath, lease);
|
|
217
|
+
const state = transactionState(transaction);
|
|
218
|
+
const current = state.records[transition.permitId];
|
|
219
|
+
if (!current) {
|
|
220
|
+
throw new Error(`Sanctuary authority permit ${transition.permitId} is missing`);
|
|
221
|
+
}
|
|
222
|
+
if (current.state !== "reserved") {
|
|
223
|
+
throw new Error(`Sanctuary authority permit ${transition.permitId} state changed`);
|
|
224
|
+
}
|
|
225
|
+
const record = {
|
|
226
|
+
...current,
|
|
227
|
+
state: transition.state,
|
|
228
|
+
outcomeDigest: transition.outcomeDigest,
|
|
229
|
+
updatedAt: transition.updatedAt,
|
|
230
|
+
};
|
|
231
|
+
state.records[record.permitId] = record;
|
|
232
|
+
this.#write(state, transaction.revision, lease);
|
|
233
|
+
return record;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
#write(state, expectedRevision, lease) {
|
|
237
|
+
fs.chmodSync(path.dirname(this.#ledgerPath), 0o700);
|
|
238
|
+
(0, session_transaction_1.writeSessionTransaction)(this.#ledgerPath, state, { lease, expectedRevision });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.FileSanctuaryAuthorityLedger = FileSanctuaryAuthorityLedger;
|