@wtfalch/foundry 0.76.0 → 0.77.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/dist/app-canary-worker.d.ts +35 -0
- package/dist/app-canary-worker.js +56 -0
- package/dist/audit/connector.d.ts +6 -2
- package/dist/audit/connector.js +3 -2
- package/dist/audit/evidence.d.ts +4 -0
- package/dist/audit/evidence.js +14 -2
- package/dist/audit/host-store.d.ts +58 -0
- package/dist/audit/host-store.js +147 -0
- package/dist/audit/host.d.ts +53 -0
- package/dist/audit/host.js +294 -0
- package/dist/audit/index.d.ts +2 -0
- package/dist/audit/index.js +2 -0
- package/dist/audit/registry.d.ts +14 -0
- package/dist/audit/sealer-role.d.ts +12 -0
- package/dist/audit/sealer-role.js +26 -2
- package/dist/audit/tick.d.ts +10 -2
- package/dist/audit/tick.js +15 -2
- package/dist/catalogue/copy-en.js +5 -0
- package/dist/catalogue/integrations.js +5 -0
- package/dist/entry-rules.d.ts +35 -0
- package/dist/entry-rules.js +35 -0
- package/dist/hosted-label.js +2 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +6 -1
- package/dist/migrations/0072_audit_host.sql +46 -0
- package/dist/migrations/0074_platform_app_canary.sql +11 -0
- package/dist/modules/ai.js +15 -1
- package/dist/modules/assistants.js +3 -0
- package/dist/modules/email.d.ts +6 -1
- package/dist/modules/email.js +88 -5
- package/dist/modules/files.js +79 -2
- package/dist/modules/index.js +7 -0
- package/dist/modules/jobs.d.ts +5 -1
- package/dist/modules/jobs.js +74 -4
- package/dist/modules/reporting.d.ts +9 -0
- package/dist/modules/reporting.js +29 -0
- package/dist/modules/sign-in.js +20 -2
- package/dist/modules/types.d.ts +2 -0
- package/dist/modules/workspaces.js +3 -0
- package/dist/org.d.ts +114 -13
- package/dist/org.js +233 -20
- package/dist/plan/ai-boule-key-apply.d.ts +65 -0
- package/dist/plan/ai-boule-key-apply.js +169 -0
- package/dist/plan/ai-boule-key.d.ts +89 -0
- package/dist/plan/ai-boule-key.js +195 -0
- package/dist/plan/analytics.d.ts +8 -1
- package/dist/plan/analytics.js +8 -1
- package/dist/plan/app-canary-apply.d.ts +49 -0
- package/dist/plan/app-canary-apply.js +232 -0
- package/dist/plan/app-canary.d.ts +44 -0
- package/dist/plan/app-canary.js +110 -0
- package/dist/plan/blue-green-apply.d.ts +6 -2
- package/dist/plan/blue-green-apply.js +14 -3
- package/dist/plan/broker-entries.js +20 -5
- package/dist/plan/company-backups.js +2 -0
- package/dist/plan/desktop-auth.d.ts +17 -4
- package/dist/plan/desktop-auth.js +22 -5
- package/dist/plan/diff.d.ts +22 -0
- package/dist/plan/diff.js +231 -18
- package/dist/plan/entry-files-key.d.ts +39 -3
- package/dist/plan/entry-files-key.js +103 -6
- package/dist/plan/entry-jobs-boule-key-apply.d.ts +65 -0
- package/dist/plan/entry-jobs-boule-key-apply.js +192 -0
- package/dist/plan/entry-jobs-boule-key.d.ts +59 -0
- package/dist/plan/entry-jobs-boule-key.js +141 -0
- package/dist/plan/entry-jobs.d.ts +2 -0
- package/dist/plan/entry-reporting-key.d.ts +36 -0
- package/dist/plan/entry-reporting-key.js +136 -0
- package/dist/plan/executor.d.ts +108 -1
- package/dist/plan/executor.js +676 -24
- package/dist/plan/files-boule-key-apply.d.ts +73 -0
- package/dist/plan/files-boule-key-apply.js +223 -0
- package/dist/plan/files-boule-key.d.ts +92 -0
- package/dist/plan/files-boule-key.js +175 -0
- package/dist/plan/hostnames.js +4 -0
- package/dist/plan/index.d.ts +4 -0
- package/dist/plan/index.js +2 -0
- package/dist/plan/jobs-scheduler-http.d.ts +13 -0
- package/dist/plan/jobs-scheduler-http.js +39 -0
- package/dist/plan/live.d.ts +1 -1
- package/dist/plan/locked-hostnames.d.ts +1 -1
- package/dist/plan/locked-hostnames.js +4 -1
- package/dist/plan/login-host.js +2 -2
- package/dist/plan/org-names.js +3 -0
- package/dist/plan/own-diff.js +3 -2
- package/dist/plan/own-jobs-route.d.ts +5 -2
- package/dist/plan/own-jobs-route.js +6 -2
- package/dist/plan/own-limits.d.ts +6 -0
- package/dist/plan/own-limits.js +1 -0
- package/dist/plan/plan-apply.d.ts +24 -0
- package/dist/plan/plan-apply.js +39 -4
- package/dist/plan/platform-diff.d.ts +2 -0
- package/dist/plan/platform-diff.js +15 -1
- package/dist/plan/platform-providers.d.ts +9 -0
- package/dist/plan/platform-providers.js +21 -0
- package/dist/plan/providers-readers.js +25 -1
- package/dist/plan/registered-app-env.d.ts +31 -0
- package/dist/plan/registered-app-env.js +63 -0
- package/dist/plan/registered-app.d.ts +121 -0
- package/dist/plan/registered-app.js +262 -0
- package/dist/plan/release-host.js +2 -1
- package/dist/plan/reporting-boule.d.ts +18 -0
- package/dist/plan/reporting-boule.js +44 -0
- package/dist/plan/reporting.d.ts +90 -0
- package/dist/plan/reporting.js +135 -0
- package/dist/plan/stage-env.js +1 -0
- package/dist/plan/stage-isolation.d.ts +3 -1
- package/dist/plan/stage-isolation.js +20 -2
- package/dist/plan/stage-services.d.ts +3 -1
- package/dist/plan/stage-services.js +8 -1
- package/dist/plan/stage-sink.d.ts +2 -2
- package/dist/plan/stage-sink.js +2 -2
- package/dist/plan/workspace-client.d.ts +1 -1
- package/dist/plan/workspace-client.js +2 -1
- package/dist/plan/workspace-sign-in.d.ts +1 -1
- package/dist/plan/workspace-sign-in.js +39 -7
- package/dist/platform.d.ts +25 -0
- package/dist/platform.js +20 -0
- package/dist/service-labels.d.ts +8 -0
- package/dist/service-labels.js +8 -0
- package/dist/slug.d.ts +1 -1
- package/dist/slug.js +5 -2
- package/dist/store/postgres.js +13 -0
- package/dist/store/schema.d.ts +485 -2
- package/dist/store/schema.js +38 -1
- package/dist/worker-app-config.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { appsOn } from '../org.js';
|
|
3
|
+
import { createAuditEvidence } from './evidence.js';
|
|
4
|
+
import { auditLedgerPasswordRef, companyAuditLedgers, } from './host-store.js';
|
|
5
|
+
import { appLedgerName, describeRetention, registeredAppLedgers, } from './registry.js';
|
|
6
|
+
import { AUDIT_SIGNER_REF, signerFromSeed } from './signer.js';
|
|
7
|
+
export const AUDIT_HOST_MAX_LEDGERS = 500;
|
|
8
|
+
export const AUDIT_HOST_MAX_BUNDLE_BYTES = 10_000_000;
|
|
9
|
+
const MAX_BODY_BYTES = 16_384;
|
|
10
|
+
const APP = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
11
|
+
const PG_NAME = /^[A-Za-z_][A-Za-z0-9_$-]{0,62}$/;
|
|
12
|
+
const HOST = /^[A-Za-z0-9]([A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$|^\[[0-9A-Fa-f:.]+\]$/;
|
|
13
|
+
const registrationSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
copy: z.literal('live'),
|
|
16
|
+
database: z
|
|
17
|
+
.object({
|
|
18
|
+
host: z.string().regex(HOST),
|
|
19
|
+
port: z.number().int().min(1).max(65535),
|
|
20
|
+
database: z.string().regex(PG_NAME),
|
|
21
|
+
user: z.string().regex(PG_NAME),
|
|
22
|
+
password: z.string().min(16).max(1024),
|
|
23
|
+
sslmode: z
|
|
24
|
+
.enum(['disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full'])
|
|
25
|
+
.optional(),
|
|
26
|
+
})
|
|
27
|
+
.strict(),
|
|
28
|
+
})
|
|
29
|
+
.strict();
|
|
30
|
+
const answer = (status, body) => new Response(JSON.stringify(body), {
|
|
31
|
+
status,
|
|
32
|
+
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },
|
|
33
|
+
});
|
|
34
|
+
const refuse = (status, code, message) => answer(status, { error: { code, message } });
|
|
35
|
+
const NO_LOG = 'There is no audit log for this app.';
|
|
36
|
+
const BAD_KEY = 'This key does not open an audit log.';
|
|
37
|
+
/** The fields of a registration that did not fit, by name only: a value is never echoed. */
|
|
38
|
+
function badFields(error) {
|
|
39
|
+
const names = [...new Set(error.issues.map((issue) => issue.path.join('.') || 'body'))];
|
|
40
|
+
return `These fields are missing or not right: ${names.slice(0, 6).join(', ')}.`;
|
|
41
|
+
}
|
|
42
|
+
function bearer(request) {
|
|
43
|
+
const header = request.headers.get('authorization') ?? '';
|
|
44
|
+
const match = /^Bearer ([^\s]+)$/.exec(header.trim());
|
|
45
|
+
return match?.[1] ?? null;
|
|
46
|
+
}
|
|
47
|
+
function wholeFrom1(value) {
|
|
48
|
+
if (value === null)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (!/^[1-9][0-9]{0,15}$/.test(value))
|
|
51
|
+
return null;
|
|
52
|
+
const n = Number(value);
|
|
53
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
54
|
+
}
|
|
55
|
+
async function readBody(request) {
|
|
56
|
+
const length = Number(request.headers.get('content-length'));
|
|
57
|
+
if (Number.isFinite(length) && length > MAX_BODY_BYTES)
|
|
58
|
+
return 'too_big';
|
|
59
|
+
let text;
|
|
60
|
+
try {
|
|
61
|
+
text = await request.text();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return 'unreadable';
|
|
65
|
+
}
|
|
66
|
+
if (Buffer.byteLength(text) > MAX_BODY_BYTES)
|
|
67
|
+
return 'too_big';
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(text);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return 'unreadable';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** The handler a host mounts: a web `Request` in, a `Response` out (a Next.js route handler, a Worker, `node:http` through an adapter). */
|
|
76
|
+
export function createAuditHostHandler(options) {
|
|
77
|
+
const log = options.log ?? ((line) => console.info(line));
|
|
78
|
+
const now = options.now ?? (() => new Date());
|
|
79
|
+
const base = (options.basePath ?? '').replace(/\/+$/, '');
|
|
80
|
+
async function signerPublicKey() {
|
|
81
|
+
try {
|
|
82
|
+
const seed = await options.heldKeys.resolve(AUDIT_SIGNER_REF);
|
|
83
|
+
return seed ? signerFromSeed(seed).publicKey : null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function company(request) {
|
|
90
|
+
const key = bearer(request);
|
|
91
|
+
if (!key)
|
|
92
|
+
return refuse(401, 'unauthorized', BAD_KEY);
|
|
93
|
+
let orgId;
|
|
94
|
+
try {
|
|
95
|
+
orgId = await options.store.companyOfKey(key);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
log('audit host: a key could not be checked');
|
|
99
|
+
return refuse(503, 'unavailable', 'The audit log service could not check the key. Try again in a minute.');
|
|
100
|
+
}
|
|
101
|
+
if (!orgId)
|
|
102
|
+
return refuse(401, 'unauthorized', BAD_KEY);
|
|
103
|
+
const org = await options.orgs.get(orgId).catch(() => null);
|
|
104
|
+
if (!org)
|
|
105
|
+
return refuse(403, 'forbidden', BAD_KEY);
|
|
106
|
+
return { org };
|
|
107
|
+
}
|
|
108
|
+
async function ledgersOf(org) {
|
|
109
|
+
const recorded = (await options.orgs.getAppliedState(org.id))?.serviceIds ?? {};
|
|
110
|
+
return companyAuditLedgers(org, recorded, options.policy, options.store);
|
|
111
|
+
}
|
|
112
|
+
async function register(org, app, request) {
|
|
113
|
+
const body = await readBody(request);
|
|
114
|
+
if (body === 'too_big')
|
|
115
|
+
return refuse(413, 'invalid_request', 'The request is too large.');
|
|
116
|
+
if (body === 'unreadable')
|
|
117
|
+
return refuse(400, 'invalid_request', 'The request is not JSON.');
|
|
118
|
+
const parsed = registrationSchema.safeParse(body);
|
|
119
|
+
if (!parsed.success)
|
|
120
|
+
return refuse(400, 'invalid_request', badFields(parsed.error));
|
|
121
|
+
const { database } = parsed.data;
|
|
122
|
+
options.secret?.(database.password);
|
|
123
|
+
if (!appsOn(org)) {
|
|
124
|
+
return refuse(403, 'forbidden', 'This company does not run apps in Boule, so it registers no log.');
|
|
125
|
+
}
|
|
126
|
+
const recorded = (await options.orgs.getAppliedState(org.id))?.serviceIds ?? {};
|
|
127
|
+
if (registeredAppLedgers(org, recorded, options.policy).some((l) => l.entryKey === app)) {
|
|
128
|
+
return refuse(409, 'conflict', "This app's log is already sealed by the company's own plan.");
|
|
129
|
+
}
|
|
130
|
+
const passwordRef = auditLedgerPasswordRef(org.id, app);
|
|
131
|
+
try {
|
|
132
|
+
// The password first: a row never names a ref with nothing held. A repeat PUT replaces both.
|
|
133
|
+
await options.heldKeys.put(passwordRef, database.password);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
log(`audit host: the sealing password for ${org.slug}/${app} could not be held`);
|
|
137
|
+
return refuse(503, 'unavailable', 'The log could not be registered. Try again in a minute.');
|
|
138
|
+
}
|
|
139
|
+
let kept;
|
|
140
|
+
try {
|
|
141
|
+
kept = await options.store.putRegistration({
|
|
142
|
+
orgId: org.id,
|
|
143
|
+
entryKey: app,
|
|
144
|
+
copy: 'live',
|
|
145
|
+
database: {
|
|
146
|
+
host: database.host,
|
|
147
|
+
port: database.port,
|
|
148
|
+
database: database.database,
|
|
149
|
+
...(database.sslmode ? { sslmode: database.sslmode } : {}),
|
|
150
|
+
},
|
|
151
|
+
user: database.user,
|
|
152
|
+
passwordRef,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
log(`audit host: the registration of ${org.slug}/${app} could not be kept`);
|
|
157
|
+
return refuse(503, 'unavailable', 'The log could not be registered. Try again in a minute.');
|
|
158
|
+
}
|
|
159
|
+
log(`audit host: ${org.slug}/${app} registered for sealing (${kept})`);
|
|
160
|
+
return answer(kept === 'created' ? 201 : 200, { ledger: appLedgerName(org, app) });
|
|
161
|
+
}
|
|
162
|
+
async function read(org, app, tail, url) {
|
|
163
|
+
const ledger = (await ledgersOf(org)).find((l) => l.entryKey === app);
|
|
164
|
+
if (!ledger)
|
|
165
|
+
return refuse(404, 'not_found', NO_LOG);
|
|
166
|
+
const publicKey = await signerPublicKey();
|
|
167
|
+
if (!publicKey) {
|
|
168
|
+
log('audit host: the platform signer key is not held');
|
|
169
|
+
return refuse(503, 'unavailable', 'The log cannot be read yet. Try again later.');
|
|
170
|
+
}
|
|
171
|
+
const evidence = createAuditEvidence({
|
|
172
|
+
connector: options.connector,
|
|
173
|
+
signerPublicKey: publicKey,
|
|
174
|
+
});
|
|
175
|
+
const unavailable = (why) => refuse(503, 'unavailable', why);
|
|
176
|
+
switch (tail) {
|
|
177
|
+
case 'status': {
|
|
178
|
+
const status = await evidence.status(ledger);
|
|
179
|
+
if (!status.ok)
|
|
180
|
+
return unavailable(status.why);
|
|
181
|
+
let seals = [];
|
|
182
|
+
try {
|
|
183
|
+
seals = await options.store.seals(org.id, app, now());
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
log(`audit host: the seals of ${org.slug}/${app} could not be read`);
|
|
187
|
+
}
|
|
188
|
+
return answer(200, { ...status.value, seals });
|
|
189
|
+
}
|
|
190
|
+
case 'checkpoints': {
|
|
191
|
+
const checkpoints = await evidence.checkpoints(ledger);
|
|
192
|
+
return checkpoints.ok
|
|
193
|
+
? answer(200, { checkpoints: checkpoints.value })
|
|
194
|
+
: unavailable(checkpoints.why);
|
|
195
|
+
}
|
|
196
|
+
case 'anchors': {
|
|
197
|
+
const anchors = await evidence.anchors(ledger);
|
|
198
|
+
return anchors.ok ? answer(200, { anchors: anchors.value }) : unavailable(anchors.why);
|
|
199
|
+
}
|
|
200
|
+
case 'bundle': {
|
|
201
|
+
const from = wholeFrom1(url.searchParams.get('from'));
|
|
202
|
+
const to = wholeFrom1(url.searchParams.get('to'));
|
|
203
|
+
if (from === null || to === null || (from !== undefined && to !== undefined && from > to)) {
|
|
204
|
+
return refuse(400, 'invalid_request', 'Pick whole numbers from 1 for the range, the first no larger than the last.');
|
|
205
|
+
}
|
|
206
|
+
const bundle = await evidence.bundle(ledger, {
|
|
207
|
+
...(from === undefined ? {} : { from }),
|
|
208
|
+
...(to === undefined ? {} : { to }),
|
|
209
|
+
});
|
|
210
|
+
if (!bundle.ok) {
|
|
211
|
+
return bundle.refused
|
|
212
|
+
? refuse(400, 'invalid_request', bundle.why)
|
|
213
|
+
: unavailable(bundle.why);
|
|
214
|
+
}
|
|
215
|
+
const bytes = Object.values(bundle.value.files).reduce((n, text) => n + Buffer.byteLength(text), 0);
|
|
216
|
+
if (bytes > AUDIT_HOST_MAX_BUNDLE_BYTES) {
|
|
217
|
+
return refuse(400, 'invalid_request', 'This export is too large to give out in one piece. Pick a narrower range of entries and try again.');
|
|
218
|
+
}
|
|
219
|
+
return answer(200, { format: bundle.value.format, files: bundle.value.files });
|
|
220
|
+
}
|
|
221
|
+
default:
|
|
222
|
+
return refuse(404, 'not_found', NO_LOG);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return async (request) => {
|
|
226
|
+
let url;
|
|
227
|
+
try {
|
|
228
|
+
url = new URL(request.url);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return refuse(400, 'invalid_request', 'The address is not readable.');
|
|
232
|
+
}
|
|
233
|
+
const path = url.pathname;
|
|
234
|
+
if (base !== '' && !(path === base || path.startsWith(`${base}/`))) {
|
|
235
|
+
return refuse(404, 'not_found', 'There is nothing here.');
|
|
236
|
+
}
|
|
237
|
+
const rest = path
|
|
238
|
+
.slice(base.length)
|
|
239
|
+
.split('/')
|
|
240
|
+
.filter((part) => part !== '');
|
|
241
|
+
if (rest[0] !== 'v1' || rest[1] !== 'audit' || rest[2] !== 'ledgers' || rest.length > 5) {
|
|
242
|
+
return refuse(404, 'not_found', 'There is nothing here.');
|
|
243
|
+
}
|
|
244
|
+
const [, , , app, tail] = rest;
|
|
245
|
+
const method = request.method.toUpperCase();
|
|
246
|
+
const allowed = app === undefined || tail !== undefined ? ['GET'] : ['PUT'];
|
|
247
|
+
if (!allowed.includes(method)) {
|
|
248
|
+
return new Response(JSON.stringify({
|
|
249
|
+
error: { code: 'method_not_allowed', message: 'This address does not take that.' },
|
|
250
|
+
}), {
|
|
251
|
+
status: 405,
|
|
252
|
+
headers: {
|
|
253
|
+
'content-type': 'application/json; charset=utf-8',
|
|
254
|
+
'cache-control': 'no-store',
|
|
255
|
+
allow: allowed.join(', '),
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const who = await company(request);
|
|
260
|
+
if (who instanceof Response)
|
|
261
|
+
return who;
|
|
262
|
+
const { org } = who;
|
|
263
|
+
try {
|
|
264
|
+
if (app === undefined) {
|
|
265
|
+
const ledgers = (await ledgersOf(org)).slice(0, AUDIT_HOST_MAX_LEDGERS);
|
|
266
|
+
return answer(200, {
|
|
267
|
+
ledgers: ledgers.map((l) => ({
|
|
268
|
+
app: l.entryKey,
|
|
269
|
+
ledger: l.ledger,
|
|
270
|
+
retention: describeRetention(l.retention),
|
|
271
|
+
retentionMonths: l.retention.months,
|
|
272
|
+
legalHold: l.retention.legalHold,
|
|
273
|
+
})),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
let decoded;
|
|
277
|
+
try {
|
|
278
|
+
decoded = decodeURIComponent(app);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return refuse(404, 'not_found', NO_LOG);
|
|
282
|
+
}
|
|
283
|
+
if (!APP.test(decoded) || decoded !== app)
|
|
284
|
+
return refuse(404, 'not_found', NO_LOG);
|
|
285
|
+
if (tail === undefined)
|
|
286
|
+
return await register(org, decoded, request);
|
|
287
|
+
return await read(org, decoded, tail, url);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
log(`audit host: a request for ${org.slug} failed`);
|
|
291
|
+
return refuse(503, 'unavailable', 'The audit log service could not answer. Try again in a minute.');
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
package/dist/audit/index.d.ts
CHANGED
package/dist/audit/index.js
CHANGED
package/dist/audit/registry.d.ts
CHANGED
|
@@ -27,6 +27,20 @@ export interface AppLedger {
|
|
|
27
27
|
retention: LedgerRetention;
|
|
28
28
|
/** The database role the sealing service connects as (`audit/sealer-role.ts`), once the apply recorded it. */
|
|
29
29
|
sealerRole?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Only for a ledger a company's Boule registered at the audit host (`PUT /v1/audit/ledgers/:app`): where its
|
|
32
|
+
* database is. A ledger of foundry's own plan has none; the host knows that database itself.
|
|
33
|
+
*/
|
|
34
|
+
database?: LedgerAddress;
|
|
35
|
+
/** The held-key ref of the sealing password, when it is not `auditSealerRef(orgId, entryKey)` (a registered ledger). */
|
|
36
|
+
passwordRef?: string;
|
|
37
|
+
}
|
|
38
|
+
/** Where a registered ledger's database is. No credential. */
|
|
39
|
+
export interface LedgerAddress {
|
|
40
|
+
host: string;
|
|
41
|
+
port: number;
|
|
42
|
+
database: string;
|
|
43
|
+
sslmode?: string;
|
|
30
44
|
}
|
|
31
45
|
/** What the host knows about retention. The plan's period is the host's (billing) to say; foundry holds no default. */
|
|
32
46
|
export interface AuditPolicy {
|
|
@@ -29,5 +29,17 @@ export interface SealerRoleSql {
|
|
|
29
29
|
* insert into the checkpoint, signing-key and anchor tables and run the sealing functions (leaves, key retirement, pending erasures, erasure sealing), and holds nothing
|
|
30
30
|
* else. The grants are listed here and in `AUDIT_SEALER_GRANTS`; `audit/sealer-role.test.ts` runs a real
|
|
31
31
|
* checkpoint as this role to prove the list is enough.
|
|
32
|
+
*
|
|
33
|
+
* It runs as an owner that is not a superuser (foundry#1110, found by boule#656). PostgreSQL 16 lets only a
|
|
34
|
+
* superuser name SUPERUSER or BYPASSRLS in `ALTER ROLE`, even to turn them off, so the role is made without any
|
|
35
|
+
* attribute (`CREATE ROLE` defaults to none of them), only LOGIN, INHERIT and the password are set, and the
|
|
36
|
+
* attributes are then read back: a role that is a superuser, bypasses row security, may create databases or
|
|
37
|
+
* roles, replicates, inherits or is a member of another role is refused before it gets a grant, naming the role
|
|
38
|
+
* and nothing else.
|
|
32
39
|
*/
|
|
33
40
|
export declare function sealerRoleSql(user: string, password: string): SealerRoleSql;
|
|
41
|
+
/**
|
|
42
|
+
* The read-back of {@link sealerRoleSql}: raises when the role can do more than seal. Its message names the
|
|
43
|
+
* role only, never the password.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sealerRoleCheckSql(user: string): string;
|
|
@@ -39,14 +39,22 @@ const literal = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
|
39
39
|
* insert into the checkpoint, signing-key and anchor tables and run the sealing functions (leaves, key retirement, pending erasures, erasure sealing), and holds nothing
|
|
40
40
|
* else. The grants are listed here and in `AUDIT_SEALER_GRANTS`; `audit/sealer-role.test.ts` runs a real
|
|
41
41
|
* checkpoint as this role to prove the list is enough.
|
|
42
|
+
*
|
|
43
|
+
* It runs as an owner that is not a superuser (foundry#1110, found by boule#656). PostgreSQL 16 lets only a
|
|
44
|
+
* superuser name SUPERUSER or BYPASSRLS in `ALTER ROLE`, even to turn them off, so the role is made without any
|
|
45
|
+
* attribute (`CREATE ROLE` defaults to none of them), only LOGIN, INHERIT and the password are set, and the
|
|
46
|
+
* attributes are then read back: a role that is a superuser, bypasses row security, may create databases or
|
|
47
|
+
* roles, replicates, inherits or is a member of another role is refused before it gets a grant, naming the role
|
|
48
|
+
* and nothing else.
|
|
42
49
|
*/
|
|
43
50
|
export function sealerRoleSql(user, password) {
|
|
44
51
|
const role = ident(user);
|
|
45
52
|
const tables = AUDIT_SEALER_INSERT_TABLES.map(ident).join(', ');
|
|
46
53
|
return {
|
|
47
54
|
statements: [
|
|
48
|
-
`do $$ begin if not exists (select from pg_roles where rolname = ${literal(user)}) then create role ${role} login; end if; end $$`,
|
|
49
|
-
`alter role ${role} with login password ${literal(password)}
|
|
55
|
+
`do $$ begin if not exists (select from pg_roles where rolname = ${literal(user)}) then create role ${role} with login noinherit; end if; end $$`,
|
|
56
|
+
`alter role ${role} with login noinherit password ${literal(password)}`,
|
|
57
|
+
sealerRoleCheckSql(user),
|
|
50
58
|
`grant usage on schema public to ${role}`,
|
|
51
59
|
`grant select, insert on ${tables} to ${role}`,
|
|
52
60
|
`revoke update, delete, truncate on ${tables} from ${role}`,
|
|
@@ -54,3 +62,19 @@ export function sealerRoleSql(user, password) {
|
|
|
54
62
|
],
|
|
55
63
|
};
|
|
56
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The read-back of {@link sealerRoleSql}: raises when the role can do more than seal. Its message names the
|
|
67
|
+
* role only, never the password.
|
|
68
|
+
*/
|
|
69
|
+
export function sealerRoleCheckSql(user) {
|
|
70
|
+
const name = literal(user);
|
|
71
|
+
return [
|
|
72
|
+
'do $$ begin if exists (select from pg_roles r where r.rolname = ',
|
|
73
|
+
name,
|
|
74
|
+
' and (r.rolsuper or r.rolbypassrls or r.rolcreatedb or r.rolcreaterole or r.rolreplication',
|
|
75
|
+
' or r.rolinherit or not r.rolcanlogin',
|
|
76
|
+
' or exists (select from pg_auth_members m where m.member = r.oid))) then',
|
|
77
|
+
` raise exception 'the sealing role % can do more than seal; make it again without other rights', ${name};`,
|
|
78
|
+
' end if; end $$',
|
|
79
|
+
].join('');
|
|
80
|
+
}
|
package/dist/audit/tick.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { DeclaredOrg } from '../org.js';
|
|
2
2
|
import type { LedgerConnector } from './connector.js';
|
|
3
|
-
import { type
|
|
3
|
+
import { type AuditHostStore } from './host-store.js';
|
|
4
|
+
import type { AuditPolicy } from './registry.js';
|
|
4
5
|
import { type SealResult, type TimestampAuthority } from './seal.js';
|
|
5
6
|
/**
|
|
6
7
|
* The scheduled sealing run (foundry#929). Foundry has no scheduler: the host calls
|
|
7
8
|
* `FoundryPlanApply.runAuditSealTick()` every {@link AUDIT_SEAL_EVERY_MINUTES} minutes, the way it calls
|
|
8
9
|
* `runReleaseTick()` every 10, and that runs this for every declared org. It seals the ledgers the `audit:ledger`
|
|
9
|
-
* step registered, nothing else, and writes one log line per ledger that says what happened and, when nothing did,
|
|
10
|
+
* step registered and the ones a company's Boule registered at the audit host, nothing else, and writes one log line per ledger that says what happened and, when nothing did,
|
|
10
11
|
* why (a ledger with no new entries, an unreachable database, no timestamp authority).
|
|
11
12
|
*/
|
|
12
13
|
/** What the host gives `FoundryPlanApply` to run the tick. */
|
|
@@ -24,6 +25,13 @@ export interface AuditSealingOptions {
|
|
|
24
25
|
fetch?: typeof fetch;
|
|
25
26
|
/** One plain line per ledger and per skipped step. Defaults to `console.info`. */
|
|
26
27
|
log?: (line: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* The audit host's store (`createPostgresAuditHostStore`, foundry#1110). With it the ledgers a company's Boule
|
|
30
|
+
* registered (`PUT /v1/audit/ledgers/:app`) are sealed too, and every result of the tick is kept there for
|
|
31
|
+
* `SEAL_RESULTS_KEPT_HOURS`, which `GET .../status` shows. A store that cannot be written is one log line; the
|
|
32
|
+
* seals stand.
|
|
33
|
+
*/
|
|
34
|
+
hostStore?: Pick<AuditHostStore, 'registrations' | 'recordSeals'>;
|
|
27
35
|
}
|
|
28
36
|
export interface AuditSealTickDeps extends AuditSealingOptions {
|
|
29
37
|
orgs: readonly DeclaredOrg[];
|
package/dist/audit/tick.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { AUDIT_SIGNER_PUBLIC_KEY_SCOPE_KEY } from '../plan/entry-audit.js';
|
|
2
|
-
import {
|
|
2
|
+
import { companyAuditLedgers } from './host-store.js';
|
|
3
3
|
import { sealLedgers, } from './seal.js';
|
|
4
4
|
import { AUDIT_SIGNER_REF, signerFromSeed } from './signer.js';
|
|
5
5
|
export const NO_AUTHORITY_LINE = 'audit sealing: no timestamp authority is configured, so checkpoints are sealed and not anchored';
|
|
6
6
|
const describe = (r) => `audit seal ${r.ledger}: ${r.outcome}${r.reason ? `, ${r.reason}` : ''}`;
|
|
7
7
|
export async function runAuditSealTick(deps) {
|
|
8
|
+
const log = deps.log ?? ((line) => console.info(line));
|
|
9
|
+
const results = await sealAll(deps);
|
|
10
|
+
if (deps.hostStore && results.length > 0) {
|
|
11
|
+
try {
|
|
12
|
+
await deps.hostStore.recordSeals(results, (deps.now ?? (() => new Date()))());
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
log('audit sealing: the results could not be kept for status; the seals stand');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
20
|
+
async function sealAll(deps) {
|
|
8
21
|
const log = deps.log ?? ((line) => console.info(line));
|
|
9
22
|
const now = deps.now ?? (() => new Date());
|
|
10
23
|
const results = [];
|
|
@@ -25,7 +38,7 @@ export async function runAuditSealTick(deps) {
|
|
|
25
38
|
const due = [];
|
|
26
39
|
for (const org of deps.orgs) {
|
|
27
40
|
try {
|
|
28
|
-
const ledgers =
|
|
41
|
+
const ledgers = await companyAuditLedgers(org, await deps.recorded(org), deps.policy, deps.hostStore);
|
|
29
42
|
if (ledgers.length > 0)
|
|
30
43
|
due.push({ org, ledgers });
|
|
31
44
|
}
|
|
@@ -82,6 +82,11 @@ const INTEGRATION_COPY = {
|
|
|
82
82
|
summary: 'We count how many people visit the app and which pages they open, for each release.',
|
|
83
83
|
ours: 'Run by us',
|
|
84
84
|
},
|
|
85
|
+
reporting: {
|
|
86
|
+
name: 'Report errors',
|
|
87
|
+
summary: 'We keep the errors the app runs into, so you can see what went wrong and when.',
|
|
88
|
+
ours: 'Run by us',
|
|
89
|
+
},
|
|
85
90
|
organisations: {
|
|
86
91
|
name: 'Sign-in for organisations',
|
|
87
92
|
summary: 'Companies sign their own people in to the app.',
|
|
@@ -81,6 +81,11 @@ const EXTRA = {
|
|
|
81
81
|
price: { class: 'free', meters: [] },
|
|
82
82
|
advanced: [],
|
|
83
83
|
},
|
|
84
|
+
reporting: {
|
|
85
|
+
providers: [ours('reporting')],
|
|
86
|
+
price: { class: 'free', meters: [] },
|
|
87
|
+
advanced: [],
|
|
88
|
+
},
|
|
84
89
|
organisations: {
|
|
85
90
|
providers: [ours('organisations')],
|
|
86
91
|
price: { class: 'free', meters: [] },
|
package/dist/entry-rules.d.ts
CHANGED
|
@@ -64,3 +64,38 @@ export declare const WORKER_APP_CRONS_MAX = 3;
|
|
|
64
64
|
* first 40 characters of the input. Cloudflare's extensions (`L`, `W`, `#`) are refused.
|
|
65
65
|
*/
|
|
66
66
|
export declare function workerAppCronProblem(expression: unknown): string | null;
|
|
67
|
+
/**
|
|
68
|
+
* #1116: the settings of the Files, Jobs and Email modules. The module fields (`modules/files.ts`,
|
|
69
|
+
* `modules/jobs.ts`, `modules/email.ts`) and the org validator (`validateEntryFiles`,
|
|
70
|
+
* `validateEntryMailJobs`) read the same bounds from here, so a value the form accepts is one the org accepts.
|
|
71
|
+
* Absent from the entry means the default.
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* How many days a new share link works: one of these, or `null` for links that never stop. The files service
|
|
75
|
+
* refuses a share link more than a year ahead, so 365 is the most.
|
|
76
|
+
*/
|
|
77
|
+
export declare const FILES_SHARE_LINK_DAYS: readonly [1, 7, 30, 90, 365];
|
|
78
|
+
export declare const FILES_SHARE_LINK_DAYS_DEFAULT = 7;
|
|
79
|
+
/** The share of the space used, in percent, at which the owners are emailed. */
|
|
80
|
+
export declare const FILES_SPACE_ALERT_DEFAULT = 80;
|
|
81
|
+
export declare const PERCENT_MIN = 1;
|
|
82
|
+
export declare const PERCENT_MAX = 100;
|
|
83
|
+
/** The jobs scheduler tries a delivery 5 times today (`DEFAULT_MAX_ATTEMPTS` in wtfalch/jobs), so 4 retries. */
|
|
84
|
+
export declare const JOBS_RETRIES_DEFAULT = 4;
|
|
85
|
+
export declare const JOBS_RETRIES_MAX = 10;
|
|
86
|
+
export declare const JOBS_PAUSE_AFTER_DEFAULT = 5;
|
|
87
|
+
export declare const JOBS_PAUSE_AFTER_MAX = 100;
|
|
88
|
+
export declare const MAIL_BOUNCES_DEFAULT = 3;
|
|
89
|
+
export declare const MAIL_BOUNCES_MAX = 10;
|
|
90
|
+
export declare const MAIL_BOUNCE_ALERT_DEFAULT = 5;
|
|
91
|
+
/**
|
|
92
|
+
* A sender name: no control character and none of `< > " , ; \`, which `@wtfalch/mailer` would put
|
|
93
|
+
* unquoted into `Name <address>`. Leading and trailing spaces are refused by the validator.
|
|
94
|
+
*/
|
|
95
|
+
export declare const MAIL_FROM_NAME: RegExp;
|
|
96
|
+
export declare const MAIL_FROM_NAME_MAX = 64;
|
|
97
|
+
/** A reply-to address: one plain mailbox, no display name. */
|
|
98
|
+
export declare const MAIL_REPLY_TO: RegExp;
|
|
99
|
+
export declare const MAIL_REPLY_TO_MAX = 254;
|
|
100
|
+
/** A whole number from `min` to `max`. */
|
|
101
|
+
export declare const wholeIn: (value: unknown, min: number, max: number) => value is number;
|
package/dist/entry-rules.js
CHANGED
|
@@ -145,3 +145,38 @@ export function workerAppCronProblem(expression) {
|
|
|
145
145
|
}
|
|
146
146
|
return null;
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* #1116: the settings of the Files, Jobs and Email modules. The module fields (`modules/files.ts`,
|
|
150
|
+
* `modules/jobs.ts`, `modules/email.ts`) and the org validator (`validateEntryFiles`,
|
|
151
|
+
* `validateEntryMailJobs`) read the same bounds from here, so a value the form accepts is one the org accepts.
|
|
152
|
+
* Absent from the entry means the default.
|
|
153
|
+
*/
|
|
154
|
+
/**
|
|
155
|
+
* How many days a new share link works: one of these, or `null` for links that never stop. The files service
|
|
156
|
+
* refuses a share link more than a year ahead, so 365 is the most.
|
|
157
|
+
*/
|
|
158
|
+
export const FILES_SHARE_LINK_DAYS = [1, 7, 30, 90, 365];
|
|
159
|
+
export const FILES_SHARE_LINK_DAYS_DEFAULT = 7;
|
|
160
|
+
/** The share of the space used, in percent, at which the owners are emailed. */
|
|
161
|
+
export const FILES_SPACE_ALERT_DEFAULT = 80;
|
|
162
|
+
export const PERCENT_MIN = 1;
|
|
163
|
+
export const PERCENT_MAX = 100;
|
|
164
|
+
/** The jobs scheduler tries a delivery 5 times today (`DEFAULT_MAX_ATTEMPTS` in wtfalch/jobs), so 4 retries. */
|
|
165
|
+
export const JOBS_RETRIES_DEFAULT = 4;
|
|
166
|
+
export const JOBS_RETRIES_MAX = 10;
|
|
167
|
+
export const JOBS_PAUSE_AFTER_DEFAULT = 5;
|
|
168
|
+
export const JOBS_PAUSE_AFTER_MAX = 100;
|
|
169
|
+
export const MAIL_BOUNCES_DEFAULT = 3;
|
|
170
|
+
export const MAIL_BOUNCES_MAX = 10;
|
|
171
|
+
export const MAIL_BOUNCE_ALERT_DEFAULT = 5;
|
|
172
|
+
/**
|
|
173
|
+
* A sender name: no control character and none of `< > " , ; \`, which `@wtfalch/mailer` would put
|
|
174
|
+
* unquoted into `Name <address>`. Leading and trailing spaces are refused by the validator.
|
|
175
|
+
*/
|
|
176
|
+
export const MAIL_FROM_NAME = /^[^\p{Cc}<>",;\\]+$/u;
|
|
177
|
+
export const MAIL_FROM_NAME_MAX = 64;
|
|
178
|
+
/** A reply-to address: one plain mailbox, no display name. */
|
|
179
|
+
export const MAIL_REPLY_TO = /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)+$/u;
|
|
180
|
+
export const MAIL_REPLY_TO_MAX = 254;
|
|
181
|
+
/** A whole number from `min` to `max`. */
|
|
182
|
+
export const wholeIn = (value, min, max) => typeof value === 'number' && Number.isInteger(value) && value >= min && value <= max;
|
package/dist/hosted-label.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { checkName } from '@wtfalch/auth/names';
|
|
2
|
-
import { FOUNDRY_SERVICE_LABELS } from './service-labels.js';
|
|
2
|
+
import { FOUNDRY_SERVICE_LABELS, PLATFORM_APP_LABELS } from './service-labels.js';
|
|
3
3
|
import { RESERVED_SLUGS, RESERVED_SLUG_PREFIX, hasBarredSlugSuffix } from './slug.js';
|
|
4
4
|
/** The first-label prefix of every workspace host (`work-<slug>-<key>`). */
|
|
5
5
|
export const WORKSPACE_HOST_PREFIX = 'work-';
|
|
@@ -51,6 +51,7 @@ export function hostedLabelRefusal(label) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
if (FOUNDRY_SERVICE_LABELS.has(label) ||
|
|
54
|
+
PLATFORM_APP_LABELS.has(label) ||
|
|
54
55
|
RESERVED_SLUGS.has(label) ||
|
|
55
56
|
label.startsWith(RESERVED_SLUG_PREFIX)) {
|
|
56
57
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, HostingGithub, BlueGreenConfig, OrgBuiltinApp, BouleApp, KosmosApp, OrgRefusal, OrgRefusalCode, WorkerAppConfig, EntryGate, EntryVolume, HostingEntryUses, EntryAuth, EntryAuthRoom, SecondFactorMode, SignInMethod, EntryFiles, EntryAi, EnvValue, EnvHeldSecret, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceDatabase, OrgServiceSchema, EntryWorkspace, ServicesConfig, SignInConfig, VersionPin, VersionPinPart, } from './org.js';
|
|
2
|
-
export { APP_ENTRY_KEY, APPS_DOMAIN, DEFAULT_GATE_LIMIT, MAX_WORKSPACE_TESTERS, isLiveEntry, normalizeTesters, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS, isMainEntry, mainEntry, companyGithubFor, heldEnvRef, ownerMailboxes, ownsServices, validateBuyerIdentity, validateHosting, normalizeHostingRepos, validateMail, validateOwners, validateServices, validateCustomers, validateSignIn, validateVersionPin, validateSite, } from './org.js';
|
|
1
|
+
export type { OrgId, MailMode, MailConfig, ChatConfig, CallsConfig, IssuerConfig, SiteConfig, IssuerAppKind, IssuerApp, HostingConfig, HostingEntry, HostingGithub, BlueGreenConfig, OrgBuiltinApp, BouleApp, KosmosApp, OrgRefusal, OrgRefusalCode, WorkerAppConfig, EntryGate, EntryVolume, HostingEntryUses, EntryAudience, EntryAuth, EntryAuthRoom, SecondFactorMode, SignInMethod, EntryFiles, EntryMail, EntryJobs, EntryAi, EnvValue, EnvHeldSecret, SsoConfig, GuestsConfig, SoloConfig, StorageConfig, AiConfig, BuyerAddress, BuyerIdentity, AdoptedAppForm, AdoptedApp, OwnerIdentity, OwnerMailbox, DeclaredOrg, OrgServiceDatabase, OrgServiceSchema, EntryWorkspace, ServicesConfig, SignInConfig, VersionPin, VersionPinPart, } from './org.js';
|
|
2
|
+
export { APP_ENTRY_KEY, APPS_DOMAIN, DEFAULT_GATE_LIMIT, MAX_WORKSPACE_TESTERS, isLiveEntry, normalizeTesters, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, FILES_SETTINGS_ENV_NAMES, MAIL_SETTINGS_ENV_NAMES, entrySettingsEnv, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS, entryAudiences, isMainEntry, mainEntry, companyGithubFor, heldEnvRef, ownerMailboxes, ownsServices, signsInBoth, signsInCustomers, validateBuyerIdentity, validateHosting, normalizeHostingRepos, validateMail, validateOwners, validateServices, validateCustomers, validateSignIn, validateVersionPin, validateSite, } from './org.js';
|
|
3
3
|
export { refuseOrg } from './refuse-org.js';
|
|
4
4
|
export { workerAppHasBuild } from './worker-app-config.js';
|
|
5
5
|
export type { DeclaredPlatform, PlatformService, PlatformServiceKey, PlatformSfuService, HeartbeatsProvider, } from './platform.js';
|
|
6
6
|
export { PLATFORM_SERVICE_KEYS, HEARTBEATS_PROVIDERS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
7
|
+
export type { PlatformAppCanary } from './platform.js';
|
|
8
|
+
export { appCanaryArtifactUrl } from './platform.js';
|
|
9
|
+
export { APP_CANARY_WORKER_SHA256, APP_CANARY_WORKER_SOURCE, appCanaryShippedPin, fetchAppCanaryArtifact, } from './app-canary-worker.js';
|
|
10
|
+
export { PLATFORM_APP_CANARY_HOSTNAME, PLATFORM_APP_CANARY_HOSTNAME_SCOPE_KEY, PLATFORM_APP_CANARY_KEY, PLATFORM_APP_CANARY_LABEL, PLATFORM_APP_CANARY_SCRIPT_NAME, } from './plan/app-canary.js';
|
|
11
|
+
export type { PlatformAppCanaryDeps } from './plan/app-canary-apply.js';
|
|
7
12
|
import { type PinnedVersions } from './bootstrap/versions.js';
|
|
8
13
|
export type { PinnedVersions };
|
|
9
14
|
export declare const PINNED_VERSIONS: Readonly<PinnedVersions>;
|
|
@@ -65,7 +70,7 @@ export type { FilesHostInput, JobsHostDeps, JobsHostInput, OwnAuthInstance, Plan
|
|
|
65
70
|
export { OWN_AUTH_APPLIED_GIT_REF_SCOPE_KEY, OWN_AUTH_APPLIED_HOSTNAME_SCOPE_KEY, OWN_AUTH_BLOCKED_KEY, OWN_FILES_BLOCKED_KEY, OWN_FILES_R2_BLOCKED_KEY, OWN_JOBS_BLOCKED_KEY, OWN_MAIL_BLOCKED_KEY, planOwnAuth, planOwnFiles, planOwnFilesR2, planOwnJobs, planOwnMail, provisionFilesHost, provisionJobsHost, provisionMailServer, } from './plan/index.js';
|
|
66
71
|
export type { PlanOwnSfuOptions, SfuAppDeps, SfuAppInput } from './plan/index.js';
|
|
67
72
|
export { OWN_SFU_BLOCKED_KEY, OWN_SFU_FIREWALL_KEY, planOwnSfu, provisionSfuApp, sfuConfig, sfuCompose, } from './plan/index.js';
|
|
68
|
-
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, KeysRegistrarPort, AnalyticsPort, PlatformOrgPortSource, AgoraHostPort, AgoraHostValues, LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './plan/index.js';
|
|
73
|
+
export type { CoolifyPort, CloudflarePort, ZitadelPort, CallsSfuPort, JobsSchedulerPort, FilesOrganisationPort, AiOrganisationPort, FoundryStepExecutorPorts, KeysRegistrarPort, AnalyticsPort, ReportingPort, PlatformOrgPortSource, AgoraHostPort, AgoraHostValues, LoginHostBinding, LoginHostBindingsPort, LoginHostDiscoveryPort, } from './plan/index.js';
|
|
69
74
|
export { FoundryStepExecutor, NotBuiltError, healthUrls, loginHostnameFor } from './plan/index.js';
|
|
70
75
|
export type { StageProbePort, ReleasePreviewMembers } from './plan/stage-guards.js';
|
|
71
76
|
export type { StageCopyHostConfig } from './plan/stage-copy-host.js';
|
|
@@ -78,6 +83,9 @@ export type { EnsureSignInOrgOptions } from './plan/index.js';
|
|
|
78
83
|
export { ZITADEL_ORG_DOMAIN_SCOPE_KEY, ensureSignInOrg, registerSignInDomain, signInOrgDomain, } from './plan/index.js';
|
|
79
84
|
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
80
85
|
export { httpAnalytics } from './plan/index.js';
|
|
86
|
+
export { httpReporting } from './plan/index.js';
|
|
87
|
+
export { httpRegistrarApi } from './plan/index.js';
|
|
88
|
+
export type { RegistrarApiPort } from './plan/index.js';
|
|
81
89
|
export { httpKeysRegistrar, keysRegistrarFromConfig, KEYS_PLATFORM_TOKEN_ENV, KEYS_SERVICE_URL_ENV, } from './plan/index.js';
|
|
82
90
|
export type { HealthTarget, HealthTargetDepth, HealthTargetExpect } from './plan/index.js';
|
|
83
91
|
export { healthTargets, platformHealthTargets } from './plan/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
export { APP_ENTRY_KEY, APPS_DOMAIN, DEFAULT_GATE_LIMIT, MAX_WORKSPACE_TESTERS, isLiveEntry, normalizeTesters, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS, isMainEntry, mainEntry, companyGithubFor, heldEnvRef, ownerMailboxes, ownsServices, validateBuyerIdentity, validateHosting, normalizeHostingRepos, validateMail, validateOwners, validateServices, validateCustomers, validateSignIn, validateVersionPin, validateSite, } from './org.js';
|
|
1
|
+
export { APP_ENTRY_KEY, APPS_DOMAIN, DEFAULT_GATE_LIMIT, MAX_WORKSPACE_TESTERS, isLiveEntry, normalizeTesters, FILES_ENTRY_PERMISSIONS, AI_ENTRY_PERMISSIONS, FILES_SETTINGS_ENV_NAMES, MAIL_SETTINGS_ENV_NAMES, entrySettingsEnv, DEFAULT_MAIL_APPS, ORG_SERVICE_SCHEMAS, orgServiceDatabase, orgServiceDatabases, SFU_SLOT_MAX, VERSION_PIN_PARTS, entryAudiences, isMainEntry, mainEntry, companyGithubFor, heldEnvRef, ownerMailboxes, ownsServices, signsInBoth, signsInCustomers, validateBuyerIdentity, validateHosting, normalizeHostingRepos, validateMail, validateOwners, validateServices, validateCustomers, validateSignIn, validateVersionPin, validateSite, } from './org.js';
|
|
2
2
|
export { refuseOrg } from './refuse-org.js';
|
|
3
3
|
export { workerAppHasBuild } from './worker-app-config.js';
|
|
4
4
|
export { PLATFORM_SERVICE_KEYS, HEARTBEATS_PROVIDERS, defaultPlatformHostname, validatePlatform, } from './platform.js';
|
|
5
|
+
export { appCanaryArtifactUrl } from './platform.js';
|
|
6
|
+
export { APP_CANARY_WORKER_SHA256, APP_CANARY_WORKER_SOURCE, appCanaryShippedPin, fetchAppCanaryArtifact, } from './app-canary-worker.js';
|
|
7
|
+
export { PLATFORM_APP_CANARY_HOSTNAME, PLATFORM_APP_CANARY_HOSTNAME_SCOPE_KEY, PLATFORM_APP_CANARY_KEY, PLATFORM_APP_CANARY_LABEL, PLATFORM_APP_CANARY_SCRIPT_NAME, } from './plan/app-canary.js';
|
|
5
8
|
// archon#273: the pinned platform service versions the planner deploys, so a
|
|
6
9
|
// host can show declared against running. Readonly: it is the planner's own
|
|
7
10
|
// object, not a copy.
|
|
@@ -43,6 +46,8 @@ export { hostedLabelRefusal } from './hosted-label.js';
|
|
|
43
46
|
export { ZITADEL_ORG_DOMAIN_SCOPE_KEY, ensureSignInOrg, registerSignInDomain, signInOrgDomain, } from './plan/index.js';
|
|
44
47
|
export { HttpJobsSchedulerClient } from './plan/index.js';
|
|
45
48
|
export { httpAnalytics } from './plan/index.js';
|
|
49
|
+
export { httpReporting } from './plan/index.js';
|
|
50
|
+
export { httpRegistrarApi } from './plan/index.js';
|
|
46
51
|
export { httpKeysRegistrar, keysRegistrarFromConfig, KEYS_PLATFORM_TOKEN_ENV, KEYS_SERVICE_URL_ENV, } from './plan/index.js';
|
|
47
52
|
export { healthTargets, platformHealthTargets } from './plan/index.js';
|
|
48
53
|
export { ProvidersAiReader, ProvidersCloudflareReader, ProvidersCmsReader, ProvidersCoolifyReader, ProvidersZitadelReader, storeLoginHostResolver, storeScopeResolver, storeRecordedIds, } from './plan/index.js';
|