@atlastodo/client-core 0.1.12 → 0.1.14-rc.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/dist/api.d.ts +26 -20
- package/dist/crypto/envelope.d.ts +4 -4
- package/dist/crypto/field.d.ts +52 -9
- package/dist/crypto/index.d.ts +1 -0
- package/dist/crypto/invitePreview.d.ts +23 -0
- package/dist/fieldPolicy.d.ts +37 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +650 -108
- package/dist/legacy.d.ts +46 -0
- package/dist/realtime.d.ts +3 -2
- package/dist/scope.d.ts +1 -1
- package/dist/store.d.ts +22 -0
- package/dist/sync-client.d.ts +17 -0
- package/dist/types.d.ts +7 -1
- package/dist/wire.d.ts +100 -0
- package/package.json +1 -1
- package/test-vectors/envelope_vectors.json +171 -2
- package/test-vectors/field_policy_vectors.json +335 -38
package/dist/legacy.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The migration of rows written before sync protocol 7 (docs/decisions/0006): their plaintext kind,
|
|
3
|
+
* field names and links stay on the server until the client that holds them re-encodes them and
|
|
4
|
+
* drops the originals.
|
|
5
|
+
*
|
|
6
|
+
* The server lists what the partition still holds (`GET /sync/legacy/keys`). A key a current copy
|
|
7
|
+
* already covers (another device or member re-encoded it) is dropped as it is. Any other is
|
|
8
|
+
* re-encoded from this device's copy: every visible field and the tombstone, each with its original
|
|
9
|
+
* stamp, so the re-encoded copy changes nothing on any device, then dropped
|
|
10
|
+
* (`POST /sync/legacy/drop`). The local store is not written. An entity this device cannot re-encode
|
|
11
|
+
* yet (a value no key opens, a scope without its key, an activity of another member, a shared
|
|
12
|
+
* project of another owner) stays until a later pass or its owner's client.
|
|
13
|
+
*/
|
|
14
|
+
import type { LocalStore } from "./store.js";
|
|
15
|
+
import type { EntityKind, Operation } from "./types.js";
|
|
16
|
+
export interface LegacyKey {
|
|
17
|
+
entity: string;
|
|
18
|
+
entity_id: string;
|
|
19
|
+
covered: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface LegacyKeysPage {
|
|
22
|
+
entities: LegacyKey[];
|
|
23
|
+
next?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface LegacyTransport {
|
|
26
|
+
legacyKeys(next?: string): Promise<LegacyKeysPage>;
|
|
27
|
+
dropLegacy(keys: {
|
|
28
|
+
entity: string;
|
|
29
|
+
entity_id: string;
|
|
30
|
+
}[]): Promise<number>;
|
|
31
|
+
syncPush(ops: Operation[]): Promise<{
|
|
32
|
+
deferred?: string[];
|
|
33
|
+
}>;
|
|
34
|
+
/** Whether this account may write every field of the entity (its role, its activity). */
|
|
35
|
+
mayReencode?(kind: EntityKind, entityId: string): boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface LegacyMigration {
|
|
38
|
+
/** Entities re-encoded and dropped. */
|
|
39
|
+
reencoded: number;
|
|
40
|
+
/** Keys dropped as they were, already covered by a current copy. */
|
|
41
|
+
covered: number;
|
|
42
|
+
/** Keys left for a later pass. */
|
|
43
|
+
left: number;
|
|
44
|
+
}
|
|
45
|
+
/** One pass over the partition's legacy keys. Throws what the transport throws. */
|
|
46
|
+
export declare function migrateLegacy(store: LocalStore, transport: LegacyTransport): Promise<LegacyMigration>;
|
package/dist/realtime.d.ts
CHANGED
|
@@ -41,9 +41,10 @@ export interface RealtimeClientOptions {
|
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Sent on every request (`x-atlas-sync-protocol`) and in the WebSocket URL; the server answers 426
|
|
44
|
-
* below its minimum. 4: `__enc: 2`, 5: ticket instead of token in the URL, 6: key rotations
|
|
44
|
+
* below its minimum. 4: `__enc: 2`, 5: ticket instead of token in the URL, 6: key rotations, 7: wire
|
|
45
|
+
* kinds, scopes and field tags (docs/decisions/0006).
|
|
45
46
|
*/
|
|
46
|
-
export declare const SYNC_PROTOCOL =
|
|
47
|
+
export declare const SYNC_PROTOCOL = 7;
|
|
47
48
|
/** The protocol rides as a param since headers are unavailable. */
|
|
48
49
|
export declare function buildSyncWsUrl(baseUrl: string, ticket: string, since: number): string;
|
|
49
50
|
export declare class RealtimeClient {
|
package/dist/scope.d.ts
CHANGED
|
@@ -81,7 +81,7 @@ export interface Repair {
|
|
|
81
81
|
reason: "key" | "plaintext";
|
|
82
82
|
keyId?: string;
|
|
83
83
|
scope?: KeyScope;
|
|
84
|
-
version?: 1 | 2;
|
|
84
|
+
version?: 1 | 2 | 3;
|
|
85
85
|
}
|
|
86
86
|
/** A current value under a rotation-retired key is history and stays. */
|
|
87
87
|
export declare function confirmRepairs(store: ScopeStore, keyring: Keyring, userId: string | undefined, repairs: readonly Repair[]): Repair[];
|
package/dist/store.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local-first store. Entities are bags of fields resolved by field-level LWW over HLC (as on the
|
|
3
3
|
* server). Local mutations stamp an HLC and enqueue an {@link Operation}; remote ops merge idempotently.
|
|
4
|
+
*
|
|
5
|
+
* What the sync decoder could not attribute to a real kind waits under its wire kind (sync protocol
|
|
6
|
+
* 7): a value no key here opened ({@link SealedPlaceholder}, revived by {@link LocalStore.reviveSealed})
|
|
7
|
+
* and a tombstone of an entity not seen yet, which the entity takes over when it arrives.
|
|
4
8
|
*/
|
|
5
9
|
import { type Hlc } from "./hlc.js";
|
|
6
10
|
import type { PersistedOp, Persistence } from "./persistence.js";
|
|
7
11
|
import type { EntityKind, Operation } from "./types.js";
|
|
12
|
+
import { type SealedPlaceholder } from "./wire.js";
|
|
8
13
|
/** Superseded ops tolerated in the log before a hydrate compacts it (see `LocalStore.compact`). */
|
|
9
14
|
export declare const COMPACT_AFTER_SUPERSEDED = 1000;
|
|
10
15
|
/** The server's `MAX_FUTURE_SKEW_MS`: a later timestamp would pin the clock and get every local op refused. */
|
|
@@ -123,6 +128,10 @@ export declare class LocalStore {
|
|
|
123
128
|
private emit;
|
|
124
129
|
private merge;
|
|
125
130
|
private mergeInto;
|
|
131
|
+
/** A tombstone that arrived before its entity's kind was known, now the entity's. */
|
|
132
|
+
private inheritTombstone;
|
|
133
|
+
/** The real kind of the entity held by this id, or null. */
|
|
134
|
+
kindOf(entityId: string): EntityKind | null;
|
|
126
135
|
private state;
|
|
127
136
|
private allStates;
|
|
128
137
|
revision(entity: EntityKind): number;
|
|
@@ -132,6 +141,8 @@ export declare class LocalStore {
|
|
|
132
141
|
exists(entity: EntityKind, entityId: string): boolean;
|
|
133
142
|
rawField(entity: EntityKind, entityId: string, field: string): unknown;
|
|
134
143
|
fieldTs(entity: EntityKind, entityId: string, field: string): Hlc | null;
|
|
144
|
+
/** The entity's tombstone stamp, or null. */
|
|
145
|
+
tombstoneTs(entity: EntityKind, entityId: string): Hlc | null;
|
|
135
146
|
visibleFieldStates(entity: EntityKind, entityId: string): {
|
|
136
147
|
field: string;
|
|
137
148
|
value: unknown;
|
|
@@ -143,6 +154,17 @@ export declare class LocalStore {
|
|
|
143
154
|
value: unknown;
|
|
144
155
|
keyId: string;
|
|
145
156
|
}>(open: (entity: EntityKind, entityId: string, field: string, envelope: unknown) => O | null): (O & RevivedField)[];
|
|
157
|
+
/**
|
|
158
|
+
* Opens the values that arrived sealed under a wire kind (protocol 7) with the keys now held:
|
|
159
|
+
* each becomes its real kind's field at the same timestamp, and the sealed copy goes. Nothing is
|
|
160
|
+
* pushed.
|
|
161
|
+
*/
|
|
162
|
+
reviveSealed<O extends {
|
|
163
|
+
kind: EntityKind;
|
|
164
|
+
field: string;
|
|
165
|
+
value: unknown;
|
|
166
|
+
keyId: string;
|
|
167
|
+
}>(open: (wire: string, entityId: string, wireField: string, sealed: SealedPlaceholder) => O | null): (O & RevivedField)[];
|
|
146
168
|
private visibleFields;
|
|
147
169
|
unsyncedOps(): Operation[];
|
|
148
170
|
/** Rebuilds their entities from the remaining ops so they stop masking the server's values. */
|
package/dist/sync-client.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { ApiError } from "./api.js";
|
|
|
7
7
|
import { type RealtimeSocket } from "./realtime.js";
|
|
8
8
|
import type { BootstrapProgress, PageRepairs } from "./persistence.js";
|
|
9
9
|
import { type Repair } from "./scope.js";
|
|
10
|
+
import { type LegacyKeysPage, type LegacyMigration } from "./legacy.js";
|
|
10
11
|
import type { LocalStore } from "./store.js";
|
|
11
12
|
import type { Operation } from "./types.js";
|
|
12
13
|
/** Ops per push request; the server caps a push by body size and op count. */
|
|
@@ -45,7 +46,16 @@ export interface SyncTransport {
|
|
|
45
46
|
repairs?: Repair[];
|
|
46
47
|
};
|
|
47
48
|
confirmRepairs?(repairs: Repair[]): Repair[];
|
|
49
|
+
/** With both, rows written before protocol 7 are re-encoded and dropped (`migrateLegacy`). */
|
|
50
|
+
legacyKeys?(next?: string): Promise<LegacyKeysPage>;
|
|
51
|
+
dropLegacy?(keys: {
|
|
52
|
+
entity: string;
|
|
53
|
+
entity_id: string;
|
|
54
|
+
}[]): Promise<number>;
|
|
55
|
+
mayReencode?(kind: Operation["entity"], entityId: string): boolean;
|
|
48
56
|
}
|
|
57
|
+
/** A pass that left keys it could not re-encode yet runs again after this long. */
|
|
58
|
+
export declare const LEGACY_RETRY_MS: number;
|
|
49
59
|
export type SyncStatus = "idle" | "syncing" | "offline" | "throttled" | "live-ws";
|
|
50
60
|
export declare const THROTTLE_BASE_MS = 5000;
|
|
51
61
|
export declare const THROTTLE_MAX_MS: number;
|
|
@@ -68,6 +78,8 @@ export interface SyncClientOptions {
|
|
|
68
78
|
/** Omit for polling only. The poll reconciles messages the hub dropped for a lagging device. */
|
|
69
79
|
realtime?: SyncRealtimeOptions;
|
|
70
80
|
onRemoteApplied?: (ops: Operation[]) => void;
|
|
81
|
+
/** After each pass of the protocol 6 migration that found something to do. */
|
|
82
|
+
onLegacyMigrated?: (result: LegacyMigration) => void;
|
|
71
83
|
}
|
|
72
84
|
export interface SyncResult {
|
|
73
85
|
pushed: number;
|
|
@@ -112,6 +124,9 @@ export declare class SyncClient {
|
|
|
112
124
|
private clockSkew;
|
|
113
125
|
private resumeTimer;
|
|
114
126
|
private epoch;
|
|
127
|
+
/** Done: the partition holds no protocol 6 rows this device can move; else the next pass's time. */
|
|
128
|
+
private legacyDone;
|
|
129
|
+
private legacyNextAt;
|
|
115
130
|
constructor(store: LocalStore, transport: SyncTransport, opts?: SyncClientOptions);
|
|
116
131
|
currentCursor(): number;
|
|
117
132
|
currentStatus(): SyncStatus;
|
|
@@ -131,6 +146,8 @@ export declare class SyncClient {
|
|
|
131
146
|
private throttle;
|
|
132
147
|
private requestSync;
|
|
133
148
|
private run;
|
|
149
|
+
/** One pass of the protocol 6 migration, when due. Never fails the cycle. */
|
|
150
|
+
private migrateLegacyRows;
|
|
134
151
|
/** Applies all pages at once so listeners render only the converged state. */
|
|
135
152
|
private pullPhase;
|
|
136
153
|
private resetFromServer;
|
package/dist/types.d.ts
CHANGED
|
@@ -431,13 +431,19 @@ export interface InviteView {
|
|
|
431
431
|
email: string;
|
|
432
432
|
display_name: string;
|
|
433
433
|
} | null;
|
|
434
|
-
/**
|
|
434
|
+
/**
|
|
435
|
+
* The project's fields as the inviter's rows written before sync protocol 7 hold them, each an
|
|
436
|
+
* envelope readable only with `sealed_key`, or null. Null once those rows are migrated: see
|
|
437
|
+
* `preview`.
|
|
438
|
+
*/
|
|
435
439
|
project: {
|
|
436
440
|
name: unknown;
|
|
437
441
|
icon: unknown;
|
|
438
442
|
color: unknown;
|
|
439
443
|
kind: unknown;
|
|
440
444
|
};
|
|
445
|
+
/** What the inviter sealed under the project key for the invitee (`openInvitePreview`). */
|
|
446
|
+
preview?: import("./crypto/index.js").SealedInvitePreview | null;
|
|
441
447
|
sealed_key: {
|
|
442
448
|
key_id: string;
|
|
443
449
|
encrypted_pek: unknown;
|
package/dist/wire.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sync wire format, protocol 7 (docs/decisions/0006): what an {@link Operation} looks like to
|
|
3
|
+
* the server, and back.
|
|
4
|
+
*
|
|
5
|
+
* On the wire an op names a wire kind (`todo.sealed`, …) instead of its real kind, the scope whose
|
|
6
|
+
* key encrypted it (`scope`: a project id, or null for the account's own key), and a field that is
|
|
7
|
+
* an opaque per-entity tag unless the manifest makes it public. The real kind and field travel
|
|
8
|
+
* inside the value's v3 envelope. Rows written before protocol 7 (legacy kinds, real kind names on
|
|
9
|
+
* the wire) are read as before, and re-encoded by the migration (`migrateLegacy`), never by a
|
|
10
|
+
* repair, which would move their timestamps.
|
|
11
|
+
*/
|
|
12
|
+
import { type FieldEnvelopeV3, type KeyScope, type Keyring } from "./crypto/index.js";
|
|
13
|
+
import type { Repair, ScopeKey } from "./scope.js";
|
|
14
|
+
import type { EntityKind, Operation } from "./types.js";
|
|
15
|
+
export type WireOp = {
|
|
16
|
+
id: string;
|
|
17
|
+
/** A wire kind, or a real kind name on a row written before protocol 7. */
|
|
18
|
+
entity: string;
|
|
19
|
+
entity_id: string;
|
|
20
|
+
/** The project whose key encrypted the op; null for the account's own key. */
|
|
21
|
+
scope?: string | null;
|
|
22
|
+
/** The account that pushed it, as the server says; null for the server's own ops. */
|
|
23
|
+
pushed_by?: string | null;
|
|
24
|
+
ts: {
|
|
25
|
+
wall_ms: number;
|
|
26
|
+
counter: number;
|
|
27
|
+
node: string;
|
|
28
|
+
};
|
|
29
|
+
} & ({
|
|
30
|
+
op: "set";
|
|
31
|
+
field: string;
|
|
32
|
+
value: unknown;
|
|
33
|
+
} | {
|
|
34
|
+
op: "delete";
|
|
35
|
+
});
|
|
36
|
+
/** A v3 value no key here opened yet: kept where it arrived, with the scope it was written in. */
|
|
37
|
+
export type SealedPlaceholder = FieldEnvelopeV3 & {
|
|
38
|
+
scope: string | null;
|
|
39
|
+
};
|
|
40
|
+
export declare function isSealedPlaceholder(v: unknown): v is SealedPlaceholder;
|
|
41
|
+
/** Whether `name` is a wire kind of the current protocol (not a legacy or server-authored one). */
|
|
42
|
+
export declare function isSealedWireKind(name: string): boolean;
|
|
43
|
+
/** The wire name of `field` of `kind` entity `entityId` under `key`: its tag, or the field itself
|
|
44
|
+
* when public. */
|
|
45
|
+
export declare function wireFieldOf(key: Uint8Array, kind: string, entityId: string, field: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Encode `op` for the wire with `k`, the key of its scope. Throws for a kind no manifest names
|
|
48
|
+
* (the server would refuse it) and for server-authored kinds.
|
|
49
|
+
*/
|
|
50
|
+
export declare function encodeOp(op: Operation, k: ScopeKey): WireOp;
|
|
51
|
+
export interface OpenedSealed {
|
|
52
|
+
kind: EntityKind;
|
|
53
|
+
field: string;
|
|
54
|
+
value: unknown;
|
|
55
|
+
keyId: string;
|
|
56
|
+
scope: KeyScope;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Open a v3 value that arrived as `wire`.`wireField` of `entityId` in `scope`. Null unless a held
|
|
60
|
+
* key of that scope opens it there, its plaintext names a kind of that wire kind, and the wire field
|
|
61
|
+
* is that field's tag (or the field itself, when public).
|
|
62
|
+
*/
|
|
63
|
+
export declare function openSealed(keyring: Keyring, wire: string, entityId: string, wireField: string, scope: string | null, envelope: FieldEnvelopeV3): OpenedSealed | null;
|
|
64
|
+
/** Where the decoder finds an entity's real kind and its own entities. */
|
|
65
|
+
export interface DecodeStore {
|
|
66
|
+
/** The real kind of an entity this device holds, if it holds one by that id. */
|
|
67
|
+
kindOf(entityId: string): EntityKind | null;
|
|
68
|
+
}
|
|
69
|
+
export interface DecodeContext {
|
|
70
|
+
/** Null decodes nothing: every current value stays sealed. */
|
|
71
|
+
keyring: Keyring | null;
|
|
72
|
+
store: DecodeStore;
|
|
73
|
+
/** The legacy (protocol 6) decode of one op: the old envelope rules. */
|
|
74
|
+
openLegacy(op: Operation & {
|
|
75
|
+
op: "set";
|
|
76
|
+
}): Operation | null;
|
|
77
|
+
}
|
|
78
|
+
export interface DecodedOp {
|
|
79
|
+
op: Operation;
|
|
80
|
+
/** For a value opened from a v3 envelope: the key and scope it opened with. */
|
|
81
|
+
opened?: {
|
|
82
|
+
keyId: string;
|
|
83
|
+
scope: KeyScope;
|
|
84
|
+
};
|
|
85
|
+
/** The op arrived under a legacy kind. */
|
|
86
|
+
legacy: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Decode one page of wire ops. A delete names no real kind: it takes the kind of the entity this
|
|
90
|
+
* device or the same page knows by its id, else stays under its wire kind until one arrives
|
|
91
|
+
* (`LocalStore` hands such a tombstone on). A value no held key opens stays a
|
|
92
|
+
* {@link SealedPlaceholder} under its wire kind and tag; `reviveLockedValues` opens it once a key
|
|
93
|
+
* arrives.
|
|
94
|
+
*/
|
|
95
|
+
export declare function decodeOps(wireOps: readonly WireOp[], ctx: DecodeContext): DecodedOp[];
|
|
96
|
+
/**
|
|
97
|
+
* A key repair for a v3 value that opened under a scope other than its entity's (`expected`) or
|
|
98
|
+
* with a key other than the canonical one: `confirmRepairs` decides whether to re-write it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function sealedRepair(d: DecodedOp, expected: KeyScope | null, isCanonical: (keyId: string, scope: KeyScope) => boolean): Repair | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlastodo/client-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14-rc.1",
|
|
4
4
|
"description": "Atlas core client: hybrid logical clocks, the op log and per-field LWW merge, end-to-end encryption, the local store and the sync client",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"homepage": "https://github.com/atlastodo/atlas/tree/main/packages/client-core",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_doc": "Field envelopes and key wraps (packages/client-core/src/crypto/field.ts, envelope.ts, keyring.ts, identity.ts, asym.ts). AES-256-GCM is deterministic given the IV, so a reimplementation must both open each `envelope`/wrap and reproduce its `ct` from the recorded `iv`. Field v2: plaintext = JSON of `value`, ISO/IEC 7816-4 padded to `paddedLength` (0x80 then zeros; at least 32 bytes, then powers of two up to 1 KiB, then whole KiB); associated data = UTF-8 of JSON.stringify([\"atlas-field\", 2, entity, entity_id, field, scope, kid]). Field v1: JSON of value, unpadded, no associated data. PEK wrap v2: associated data JSON.stringify([\"atlas-pek-wrap\", 2, project_id]); the unbound form has none. project_key_id = hex of the first 16 bytes of SHA-256(\"atlas-pek-id-v1\" || pek). Signing key wrap: associated data JSON.stringify([\"atlas-signing-key-wrap\", 1, lowercase public key hex]). Sealed: X25519 with an ephemeral key, HKDF-SHA256(no salt, info 'atlas-seal-v1'), AES-256-GCM without associated data. Asserted by packages/client-core/src/crypto/envelope-vectors.test.ts and crates/atlas-client.",
|
|
2
|
+
"_doc": "Field envelopes and key wraps (packages/client-core/src/crypto/field.ts, envelope.ts, keyring.ts, identity.ts, asym.ts). AES-256-GCM is deterministic given the IV, so a reimplementation must both open each `envelope`/wrap and reproduce its `ct` from the recorded `iv`. Field v2: plaintext = JSON of `value`, ISO/IEC 7816-4 padded to `paddedLength` (0x80 then zeros; at least 32 bytes, then powers of two up to 1 KiB, then whole KiB); associated data = UTF-8 of JSON.stringify([\"atlas-field\", 2, entity, entity_id, field, scope, kid]). Field v3 (sync protocol 7, docs/decisions/0006): plaintext = JSON of [kind, field, value], padded as v2 but to at least 64 bytes (`padding_v3`); associated data = UTF-8 of JSON.stringify([\"atlas-field\", 3, wire_entity, entity_id, wire_field, scope, kid]), where wire_field is the tag or a public name. Field tags: tag_key = HKDF-SHA256(ikm key, no salt, info 'atlas-field-tag-v1', 32 bytes); tag = hex of the first 16 bytes of HMAC-SHA256(tag_key, UTF-8 of JSON.stringify([\"atlas-field-tag\", 1, entity_id, kind, field])). Field v1: JSON of value, unpadded, no associated data. PEK wrap v2: associated data JSON.stringify([\"atlas-pek-wrap\", 2, project_id]); the unbound form has none. project_key_id = hex of the first 16 bytes of SHA-256(\"atlas-pek-id-v1\" || pek). Signing key wrap: associated data JSON.stringify([\"atlas-signing-key-wrap\", 1, lowercase public key hex]). Sealed: X25519 with an ephemeral key, HKDF-SHA256(no salt, info 'atlas-seal-v1'), AES-256-GCM without associated data. Asserted by packages/client-core/src/crypto/envelope-vectors.test.ts and crates/atlas-client.",
|
|
3
3
|
"padding": [
|
|
4
4
|
{
|
|
5
5
|
"n": 0,
|
|
@@ -309,5 +309,174 @@
|
|
|
309
309
|
"ct": "0DIwXsDjSjvwJtoy2dkoUv0FliHcWGcF+yCdz6Dg9ftpREyMIQljYNTSPxjAtBtH"
|
|
310
310
|
}
|
|
311
311
|
}
|
|
312
|
-
}
|
|
312
|
+
},
|
|
313
|
+
"field_v3": [
|
|
314
|
+
{
|
|
315
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
316
|
+
"scope": "personal",
|
|
317
|
+
"entity": "todo.sealed",
|
|
318
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
319
|
+
"field": "1016533f5038961939f84e4de968f7ca",
|
|
320
|
+
"kind": "task",
|
|
321
|
+
"real_field": "title",
|
|
322
|
+
"value": "Buy milk",
|
|
323
|
+
"envelope": {
|
|
324
|
+
"__enc": 3,
|
|
325
|
+
"kid": "dek",
|
|
326
|
+
"iv": "AAECAwQFBgcICQoL",
|
|
327
|
+
"ct": "HCCieraO4DevNf7/3YxaQaGU8k3QFjYQU0W4BR1pALIBEK78r8ESmHSkf+2Ihyg47llgjVrWo9o/lyoZGOP17gyymbCdZV+TFLMtpIs5VG8="
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
332
|
+
"scope": "personal",
|
|
333
|
+
"entity": "todo.sealed",
|
|
334
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
335
|
+
"field": "d8f50043473bd8b76d68b625fb6a3aea",
|
|
336
|
+
"kind": "task",
|
|
337
|
+
"real_field": "is_completed",
|
|
338
|
+
"value": true,
|
|
339
|
+
"envelope": {
|
|
340
|
+
"__enc": 3,
|
|
341
|
+
"kid": "dek",
|
|
342
|
+
"iv": "DA0ODxAREhMUFRYX",
|
|
343
|
+
"ct": "w9wduQwd3n8VQzuK8uRLlzlmmYuSXxfe6defvWSgwPos92ol4cUmYR/zDlLurBrajFa70an0FLNO/3pcuu1KuExVasrmycgCPcLULcZAsno="
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"key": "f0e1d2c3b4a5968778695a4b3c2d1e0f00112233445566778899aabbccddeeff",
|
|
348
|
+
"scope": "project:8f9e0d1c-2b3a-4f5e-8d7c-6b5a49382716",
|
|
349
|
+
"entity": "todo.sealed",
|
|
350
|
+
"entity_id": "5d6e7f80-1a2b-4c3d-9e8f-0a1b2c3d4e5f",
|
|
351
|
+
"field": "83f03fc82fcb43d0e619d152872e5473",
|
|
352
|
+
"kind": "comment",
|
|
353
|
+
"real_field": "body",
|
|
354
|
+
"value": {
|
|
355
|
+
"mentions": [],
|
|
356
|
+
"text": "Looks good"
|
|
357
|
+
},
|
|
358
|
+
"envelope": {
|
|
359
|
+
"__enc": 3,
|
|
360
|
+
"kid": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
|
|
361
|
+
"iv": "GBkaGxwdHh8gISIj",
|
|
362
|
+
"ct": "gM0BOi3BReVqddhqLwhPCwV0kuvTv6bYHRoRgQydJjMyL35iHh3mMqFiua3j+VojoKlqiag/ErJLBG46yLqjUCE7lh7EjjVu2Fpfla1m89w="
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"key": "f0e1d2c3b4a5968778695a4b3c2d1e0f00112233445566778899aabbccddeeff",
|
|
367
|
+
"scope": "project:8f9e0d1c-2b3a-4f5e-8d7c-6b5a49382716",
|
|
368
|
+
"entity": "todo.project",
|
|
369
|
+
"entity_id": "8f9e0d1c-2b3a-4f5e-8d7c-6b5a49382716",
|
|
370
|
+
"field": "archived_at",
|
|
371
|
+
"kind": "project",
|
|
372
|
+
"real_field": "archived_at",
|
|
373
|
+
"value": 1767225600000,
|
|
374
|
+
"envelope": {
|
|
375
|
+
"__enc": 3,
|
|
376
|
+
"kid": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
|
|
377
|
+
"iv": "JCUmJygpKissLS4v",
|
|
378
|
+
"ct": "ziPpSJ2/s+FmTFVoCEiT0qOWycJ9b01Q3jZBdkoi+Nas+Fkl5HXIW+R7YEp1nTOWUF1A3TDFFo+vSVg28cnavZZrVx3Hpg48rpBM0L/sj0M="
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
383
|
+
"scope": "personal",
|
|
384
|
+
"entity": "vault.sealed",
|
|
385
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
386
|
+
"field": "2a2a666e3a097cba81ac5ab96938b0de",
|
|
387
|
+
"kind": "vault.item",
|
|
388
|
+
"real_field": "notes",
|
|
389
|
+
"value": null,
|
|
390
|
+
"envelope": {
|
|
391
|
+
"__enc": 3,
|
|
392
|
+
"kid": "dek",
|
|
393
|
+
"iv": "MDEyMzQ1Njc4OTo7",
|
|
394
|
+
"ct": "6BdHoZwDPi1DR+uBMLUcBvNTKiThEipwS9IMzOizfj5DYAxkLMCbp64kEkl6xtMtrkxdSSuxxSetQGFUQEhR9MS7KHMeOLdxaZlLD9nMEFs="
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
],
|
|
398
|
+
"field_tags": [
|
|
399
|
+
{
|
|
400
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
401
|
+
"tag_key": "572bd3fbe1e9c7e9726d8aad148784bcf83f473cfa0a69065e35b092dbc13fcb",
|
|
402
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
403
|
+
"kind": "task",
|
|
404
|
+
"field": "title",
|
|
405
|
+
"tag": "1016533f5038961939f84e4de968f7ca"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
409
|
+
"tag_key": "572bd3fbe1e9c7e9726d8aad148784bcf83f473cfa0a69065e35b092dbc13fcb",
|
|
410
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
411
|
+
"kind": "task",
|
|
412
|
+
"field": "project_id",
|
|
413
|
+
"tag": "71a930596b7083c6af5498a40a73e20f"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
417
|
+
"tag_key": "572bd3fbe1e9c7e9726d8aad148784bcf83f473cfa0a69065e35b092dbc13fcb",
|
|
418
|
+
"entity_id": "5d6e7f80-1a2b-4c3d-9e8f-0a1b2c3d4e5f",
|
|
419
|
+
"kind": "task",
|
|
420
|
+
"field": "title",
|
|
421
|
+
"tag": "fe3259da59051bd1b8f20291bc3d7d1b"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"key": "f0e1d2c3b4a5968778695a4b3c2d1e0f00112233445566778899aabbccddeeff",
|
|
425
|
+
"tag_key": "346fb253d66eba03ea9300e99e109d69b1f8e1f7b4ce2677a089a9e969eae88e",
|
|
426
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
427
|
+
"kind": "task",
|
|
428
|
+
"field": "title",
|
|
429
|
+
"tag": "5d9f33dc8e6c3acf5e7c6a81cc3b1703"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
433
|
+
"tag_key": "572bd3fbe1e9c7e9726d8aad148784bcf83f473cfa0a69065e35b092dbc13fcb",
|
|
434
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
435
|
+
"kind": "preference",
|
|
436
|
+
"field": "pin:8f9e0d1c-2b3a-4f5e-8d7c-6b5a49382716:5d6e7f80-1a2b-4c3d-9e8f-0a1b2c3d4e5f",
|
|
437
|
+
"tag": "c3682378312dc5e6ceaad26593274006"
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
441
|
+
"tag_key": "572bd3fbe1e9c7e9726d8aad148784bcf83f473cfa0a69065e35b092dbc13fcb",
|
|
442
|
+
"entity_id": "0b7c3f4e-9a1d-4c2b-8e5f-6a7b8c9d0e1f",
|
|
443
|
+
"kind": "journal.entry",
|
|
444
|
+
"field": "body",
|
|
445
|
+
"tag": "d7f595d8d67e04b6380c408ea90fbf71"
|
|
446
|
+
}
|
|
447
|
+
],
|
|
448
|
+
"padding_v3": [
|
|
449
|
+
{
|
|
450
|
+
"n": 0,
|
|
451
|
+
"padded": 64
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"n": 30,
|
|
455
|
+
"padded": 64
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"n": 62,
|
|
459
|
+
"padded": 64
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"n": 63,
|
|
463
|
+
"padded": 64
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"n": 64,
|
|
467
|
+
"padded": 128
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"n": 100,
|
|
471
|
+
"padded": 128
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"n": 1023,
|
|
475
|
+
"padded": 1024
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
"n": 1024,
|
|
479
|
+
"padded": 2048
|
|
480
|
+
}
|
|
481
|
+
]
|
|
313
482
|
}
|