@flowdular/sdk 0.3.0 → 0.3.1
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/.ai/platform-capabilities.md +2 -2
- package/.ai/policies/capabilities.yaml +30 -3
- package/.ai/references/catalog/module.json +1 -1
- package/.ai/references/catalog/package.json +2 -2
- package/.ai/references/catalog/spec/module.yaml +1 -1
- package/.ai/references/catalog.provenance.json +6 -6
- package/.ai/skills/cli-extension/SKILL.md +1 -1
- package/.ai/skills/deploy-operate/SKILL.md +7 -2
- package/modules/approvals/migrations/0005_approvals_grant_audit.down.sql +4 -0
- package/modules/approvals/migrations/0005_approvals_grant_audit.up.sql +20 -0
- package/modules/approvals/module.json +1 -1
- package/modules/approvals/package.json +1 -1
- package/modules/approvals/spec/module.yaml +11 -2
- package/modules/approvals/src/domain/capability.ts +12 -0
- package/modules/approvals/src/domain/grant.ts +69 -0
- package/modules/approvals/src/domain/types.ts +14 -0
- package/modules/approvals/src/index.ts +9 -0
- package/modules/approvals/src/platform.ts +8 -0
- package/modules/approvals/src/server/runtime.ts +4 -0
- package/modules/approvals/src/services/approvals-service.ts +80 -0
- package/modules/approvals/src/services/database-repository.ts +65 -6
- package/modules/approvals/src/services/migration.ts +34 -0
- package/modules/approvals/src/services/repository.ts +8 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.down.sql +2 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.up.sql +19 -0
- package/modules/connectors/module.json +7 -3
- package/modules/connectors/package.json +2 -1
- package/modules/connectors/spec/module.yaml +2 -1
- package/modules/connectors/src/cli/commands.json +17 -0
- package/modules/connectors/src/cli/index.ts +126 -0
- package/modules/connectors/src/services/credential-rotation.ts +221 -0
- package/modules/connectors/src/services/credential-vault.ts +6 -0
- package/modules/connectors/src/services/migration.ts +36 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.down.sql +2 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.up.sql +18 -0
- package/modules/documents/module.json +7 -3
- package/modules/documents/package.json +2 -1
- package/modules/documents/spec/module.yaml +2 -1
- package/modules/documents/src/cli/commands.json +17 -0
- package/modules/documents/src/cli/index.ts +145 -0
- package/modules/documents/src/services/database-repository.ts +15 -4
- package/modules/documents/src/services/documents-service.ts +13 -9
- package/modules/documents/src/services/migration.ts +35 -0
- package/modules/documents/src/services/repository.ts +12 -2
- package/modules/documents/src/services/storage-rotation.ts +157 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.down.sql +1 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.up.sql +9 -0
- package/modules/exports/module.json +7 -3
- package/modules/exports/package.json +2 -1
- package/modules/exports/spec/module.yaml +2 -1
- package/modules/exports/src/cli/commands.json +17 -0
- package/modules/exports/src/cli/index.ts +145 -0
- package/modules/exports/src/server/index.ts +0 -1
- package/modules/exports/src/services/data-classes.ts +16 -13
- package/modules/exports/src/services/database-repository.ts +30 -32
- package/modules/exports/src/services/migration.ts +27 -0
- package/modules/exports/src/services/repository.ts +9 -10
- package/modules/exports/src/services/storage-rotation.ts +138 -0
- package/package.json +1 -1
- package/packages/contracts/src/index.ts +1 -1
- package/packages/database/src/backup.ts +1 -0
- package/packages/database/src/migrations.ts +7 -0
- package/packages/harness/src/runtime.ts +169 -10
- package/packages/harness/src/tool-adapters.ts +6 -13
- package/packages/kernel/src/approval-grant.ts +310 -0
- package/packages/kernel/src/index.ts +20 -0
- package/packages/storage/src/envelope.ts +70 -21
- package/packages/storage/src/index.ts +7 -1
- package/packages/storage/src/port.ts +12 -1
- package/packages/storage/src/reseal.ts +128 -0
|
@@ -121,30 +121,30 @@ function describe(key: string, header: FrameHeader): StoredObject {
|
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
interface FrameDraft {
|
|
125
|
+
readonly contentType: string;
|
|
126
|
+
readonly bytes: number;
|
|
127
|
+
readonly checksum: string;
|
|
128
|
+
readonly scan: StorageScanVerdict;
|
|
129
|
+
readonly createdAt: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function sealFrame(
|
|
125
133
|
keyring: Keyring,
|
|
126
134
|
key: string,
|
|
127
135
|
plaintext: Uint8Array,
|
|
128
|
-
|
|
129
|
-
readonly contentType: string;
|
|
130
|
-
readonly scan: StorageScanVerdict;
|
|
131
|
-
readonly createdAt: Date;
|
|
132
|
-
},
|
|
136
|
+
draft: FrameDraft,
|
|
133
137
|
): { readonly frame: Uint8Array; readonly object: StoredObject } {
|
|
134
|
-
const
|
|
138
|
+
const unsealed = {
|
|
135
139
|
v: 1,
|
|
136
140
|
keyId: keyring.keyId,
|
|
137
141
|
iv: '',
|
|
138
142
|
tag: '',
|
|
139
|
-
|
|
140
|
-
bytes: plaintext.byteLength,
|
|
141
|
-
checksum: storageChecksum(plaintext),
|
|
142
|
-
scan: metadata.scan,
|
|
143
|
-
createdAt: metadata.createdAt.toISOString(),
|
|
143
|
+
...draft,
|
|
144
144
|
} satisfies FrameHeader;
|
|
145
|
-
const sealed = keyring.seal(plaintext, additionalData(key,
|
|
145
|
+
const sealed = keyring.seal(plaintext, additionalData(key, unsealed));
|
|
146
146
|
const header: FrameHeader = {
|
|
147
|
-
...
|
|
147
|
+
...unsealed,
|
|
148
148
|
keyId: sealed.keyId,
|
|
149
149
|
iv: sealed.iv.toString('base64'),
|
|
150
150
|
tag: sealed.tag.toString('base64'),
|
|
@@ -162,19 +162,30 @@ export function encodeStoredObject(
|
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
export function encodeStoredObject(
|
|
166
|
+
keyring: Keyring,
|
|
167
167
|
key: string,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
plaintext: Uint8Array,
|
|
169
|
+
metadata: {
|
|
170
|
+
readonly contentType: string;
|
|
171
|
+
readonly scan: StorageScanVerdict;
|
|
172
|
+
readonly createdAt: Date;
|
|
173
|
+
},
|
|
174
|
+
): { readonly frame: Uint8Array; readonly object: StoredObject } {
|
|
175
|
+
return sealFrame(keyring, key, plaintext, {
|
|
176
|
+
contentType: metadata.contentType,
|
|
177
|
+
bytes: plaintext.byteLength,
|
|
178
|
+
checksum: storageChecksum(plaintext),
|
|
179
|
+
scan: metadata.scan,
|
|
180
|
+
createdAt: metadata.createdAt.toISOString(),
|
|
181
|
+
});
|
|
171
182
|
}
|
|
172
183
|
|
|
173
|
-
|
|
184
|
+
function openFrame(
|
|
174
185
|
keyring: Keyring,
|
|
175
186
|
key: string,
|
|
176
187
|
frame: Uint8Array,
|
|
177
|
-
): { readonly
|
|
188
|
+
): { readonly header: FrameHeader; readonly plaintext: Buffer } {
|
|
178
189
|
const { header, ciphertextOffset } = parseHeader(frame);
|
|
179
190
|
const plaintext = keyring.open(
|
|
180
191
|
{
|
|
@@ -185,5 +196,43 @@ export function openStoredObject(
|
|
|
185
196
|
},
|
|
186
197
|
additionalData(key, header),
|
|
187
198
|
);
|
|
199
|
+
return { header, plaintext };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Metadata only. The bytes are authenticated by `openStoredObject`, not here. */
|
|
203
|
+
export function readStoredObjectHeader(
|
|
204
|
+
key: string,
|
|
205
|
+
frame: Uint8Array,
|
|
206
|
+
): StoredObject {
|
|
207
|
+
return describe(key, parseHeader(frame).header);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function openStoredObject(
|
|
211
|
+
keyring: Keyring,
|
|
212
|
+
key: string,
|
|
213
|
+
frame: Uint8Array,
|
|
214
|
+
): { readonly object: StoredObject; readonly plaintext: Buffer } {
|
|
215
|
+
const { header, plaintext } = openFrame(keyring, key, frame);
|
|
188
216
|
return { object: describe(key, header), plaintext };
|
|
189
217
|
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The same object under the current key. The frame is opened under the key it
|
|
221
|
+
* names, which authenticates the header, and the header's own fields become the
|
|
222
|
+
* additional data of the new frame, so nothing about the object changes but
|
|
223
|
+
* the key, the nonce, the tag and the ciphertext.
|
|
224
|
+
*/
|
|
225
|
+
export function resealStoredObject(
|
|
226
|
+
keyring: Keyring,
|
|
227
|
+
key: string,
|
|
228
|
+
frame: Uint8Array,
|
|
229
|
+
): { readonly frame: Uint8Array; readonly object: StoredObject } {
|
|
230
|
+
const { header, plaintext } = openFrame(keyring, key, frame);
|
|
231
|
+
return sealFrame(keyring, key, plaintext, {
|
|
232
|
+
contentType: header.contentType,
|
|
233
|
+
bytes: header.bytes,
|
|
234
|
+
checksum: header.checksum,
|
|
235
|
+
scan: header.scan,
|
|
236
|
+
createdAt: header.createdAt,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -31,7 +31,13 @@ export { createLocalObjectStore } from './local.ts';
|
|
|
31
31
|
export { createS3ObjectStore } from './s3.ts';
|
|
32
32
|
export type { S3ObjectStoreOptions } from './s3.ts';
|
|
33
33
|
export { createStoragePort } from './port.ts';
|
|
34
|
-
export type { StoragePortOptions } from './port.ts';
|
|
34
|
+
export type { ManagedStoragePort, StoragePortOptions } from './port.ts';
|
|
35
|
+
export type {
|
|
36
|
+
StorageResealCount,
|
|
37
|
+
StorageResealOptions,
|
|
38
|
+
StorageResealPort,
|
|
39
|
+
StorageResealReport,
|
|
40
|
+
} from './reseal.ts';
|
|
35
41
|
export {
|
|
36
42
|
mintStorageReadToken,
|
|
37
43
|
openStorageReadToken,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { createLocalObjectStore } from './local.ts';
|
|
25
25
|
import { createS3ObjectStore } from './s3.ts';
|
|
26
26
|
import { mintStorageReadToken, storageReadUrl } from './read-token.ts';
|
|
27
|
+
import { createStorageResealer, type StorageResealPort } from './reseal.ts';
|
|
27
28
|
import { unscannedStorageScanner, type StorageScanner } from './scanner.ts';
|
|
28
29
|
import type { StorageConfig } from './config.ts';
|
|
29
30
|
import type { ObjectStore } from './store.ts';
|
|
@@ -125,6 +126,9 @@ function objectStoreFor(
|
|
|
125
126
|
});
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/** The port a deployment builds: the module contract plus the operator pass. */
|
|
130
|
+
export interface ManagedStoragePort extends StoragePort, StorageResealPort {}
|
|
131
|
+
|
|
128
132
|
/**
|
|
129
133
|
* The one place the storage rules live: the tenant-first key layout, the size
|
|
130
134
|
* limit, the content type allowlist with magic byte verification, the scanner
|
|
@@ -133,11 +137,12 @@ function objectStoreFor(
|
|
|
133
137
|
export function createStoragePort(
|
|
134
138
|
config: StorageConfig,
|
|
135
139
|
options: StoragePortOptions,
|
|
136
|
-
):
|
|
140
|
+
): ManagedStoragePort {
|
|
137
141
|
const store = objectStoreFor(config, options);
|
|
138
142
|
const scanner = options.scanner ?? unscannedStorageScanner;
|
|
139
143
|
const clock = options.clock ?? (() => new Date());
|
|
140
144
|
const keyring = options.keyring;
|
|
145
|
+
const resealer = createStorageResealer(store, keyring);
|
|
141
146
|
let disposed = false;
|
|
142
147
|
|
|
143
148
|
const live = (): void => {
|
|
@@ -242,6 +247,12 @@ export function createStoragePort(
|
|
|
242
247
|
return prefix ? readStoredObjectHeader(key, prefix) : null;
|
|
243
248
|
},
|
|
244
249
|
|
|
250
|
+
keyId: keyring.keyId,
|
|
251
|
+
reseal(objects, options) {
|
|
252
|
+
live();
|
|
253
|
+
return resealer.reseal(objects, options);
|
|
254
|
+
},
|
|
255
|
+
|
|
245
256
|
async dispose(): Promise<void> {
|
|
246
257
|
if (disposed) return;
|
|
247
258
|
disposed = true;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { KeyringError, type Keyring } from '@flowdular/sdk/kernel';
|
|
2
|
+
import {
|
|
3
|
+
StorageError,
|
|
4
|
+
storageObjectKey,
|
|
5
|
+
type StorageObjectRef,
|
|
6
|
+
} from './contracts.ts';
|
|
7
|
+
import {
|
|
8
|
+
readStoredObjectHeader,
|
|
9
|
+
resealStoredObject,
|
|
10
|
+
STORAGE_HEADER_PREFIX_BYTES,
|
|
11
|
+
} from './envelope.ts';
|
|
12
|
+
import type { ObjectStore } from './store.ts';
|
|
13
|
+
|
|
14
|
+
export interface StorageResealCount {
|
|
15
|
+
readonly keyId: string;
|
|
16
|
+
readonly objects: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StorageResealReport {
|
|
20
|
+
/** Key id every object should end on: the current key of the ring. */
|
|
21
|
+
readonly currentKeyId: string;
|
|
22
|
+
readonly counts: readonly StorageResealCount[];
|
|
23
|
+
/** Objects on a retired key the ring still holds. */
|
|
24
|
+
readonly stale: number;
|
|
25
|
+
readonly resealed: number;
|
|
26
|
+
/** Objects sealed under a key id this ring does not hold; left as they are. */
|
|
27
|
+
readonly unknown: number;
|
|
28
|
+
/** Objects that failed authentication under the key they name; left as they are. */
|
|
29
|
+
readonly refused: number;
|
|
30
|
+
/** References with no object behind them. */
|
|
31
|
+
readonly missing: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface StorageResealOptions {
|
|
35
|
+
readonly apply: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface StorageResealPort {
|
|
39
|
+
/** Key id every re-seal writes: the current key of the ring. */
|
|
40
|
+
readonly keyId: string;
|
|
41
|
+
/**
|
|
42
|
+
* Re-seals every object of the batch that a retired key sealed, in place.
|
|
43
|
+
* The caller owns the inventory: an adapter cannot list, so the references
|
|
44
|
+
* come from the rows that name the objects. Without `apply` the headers are
|
|
45
|
+
* read and counted and nothing is written.
|
|
46
|
+
*/
|
|
47
|
+
reseal(
|
|
48
|
+
objects: readonly StorageObjectRef[],
|
|
49
|
+
options: StorageResealOptions,
|
|
50
|
+
): Promise<StorageResealReport>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* A frame the port cannot parse is left exactly as the one it cannot open:
|
|
54
|
+
the pass counts it and moves on, and the operator restores it. */
|
|
55
|
+
function corrupt(error: unknown): boolean {
|
|
56
|
+
return error instanceof StorageError && error.code === 'OBJECT_CORRUPT';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createStorageResealer(
|
|
60
|
+
store: ObjectStore,
|
|
61
|
+
keyring: Keyring,
|
|
62
|
+
): StorageResealPort {
|
|
63
|
+
return {
|
|
64
|
+
keyId: keyring.keyId,
|
|
65
|
+
async reseal(objects, options) {
|
|
66
|
+
const counts = new Map<string, number>();
|
|
67
|
+
let stale = 0;
|
|
68
|
+
let resealed = 0;
|
|
69
|
+
let unknown = 0;
|
|
70
|
+
let refused = 0;
|
|
71
|
+
let missing = 0;
|
|
72
|
+
for (const reference of objects) {
|
|
73
|
+
const key = storageObjectKey(reference);
|
|
74
|
+
const prefix = await store.read(key, STORAGE_HEADER_PREFIX_BYTES);
|
|
75
|
+
if (!prefix) {
|
|
76
|
+
missing += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
let keyId: string;
|
|
80
|
+
try {
|
|
81
|
+
keyId = readStoredObjectHeader(key, prefix).keyId;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (corrupt(error)) {
|
|
84
|
+
refused += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
counts.set(keyId, (counts.get(keyId) ?? 0) + 1);
|
|
90
|
+
if (keyId === keyring.keyId) continue;
|
|
91
|
+
if (!keyring.knows(keyId)) {
|
|
92
|
+
unknown += 1;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
stale += 1;
|
|
96
|
+
if (!options.apply) continue;
|
|
97
|
+
const frame = await store.read(key);
|
|
98
|
+
if (!frame) {
|
|
99
|
+
missing += 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
let next;
|
|
103
|
+
try {
|
|
104
|
+
next = resealStoredObject(keyring, key, frame);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof KeyringError || corrupt(error)) {
|
|
107
|
+
refused += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
await store.write(key, next.frame);
|
|
113
|
+
resealed += 1;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
currentKeyId: keyring.keyId,
|
|
117
|
+
counts: [...counts]
|
|
118
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
119
|
+
.map(([keyId, count]) => ({ keyId, objects: count })),
|
|
120
|
+
stale,
|
|
121
|
+
resealed,
|
|
122
|
+
unknown,
|
|
123
|
+
refused,
|
|
124
|
+
missing,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|