@ibgib/space-gib 0.0.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/Dockerfile +14 -0
  3. package/IMPLEMENTATION.md +484 -0
  4. package/README.md +46 -0
  5. package/dist/client/bootstrap.mjs +58 -0
  6. package/dist/client/bootstrap.mjs.map +7 -0
  7. package/dist/client/chunk-CT47Z5WU.mjs +21 -0
  8. package/dist/client/chunk-CT47Z5WU.mjs.map +7 -0
  9. package/dist/client/chunk-RHEDTRKF.mjs +235 -0
  10. package/dist/client/chunk-RHEDTRKF.mjs.map +7 -0
  11. package/dist/client/index.html +147 -0
  12. package/dist/client/index.mjs +2 -0
  13. package/dist/client/index.mjs.map +7 -0
  14. package/dist/client/script.mjs +2 -0
  15. package/dist/client/script.mjs.map +7 -0
  16. package/dist/client/style.css +605 -0
  17. package/dist/respec-gib.node.mjs +5 -0
  18. package/dist/server/server.mjs +20157 -0
  19. package/dist/server/server.mjs.map +7 -0
  20. package/generate-version-file.js +35 -0
  21. package/package.json +27 -0
  22. package/src/client/AUTO-GENERATED-version.mts +11 -0
  23. package/src/client/README.md +19 -0
  24. package/src/client/api/function-infos.web.mts +38 -0
  25. package/src/client/api/space-gib-api-bridge.mts +85 -0
  26. package/src/client/bootstrap.mts +49 -0
  27. package/src/client/components/keystone-creator/keystone-creator.css +139 -0
  28. package/src/client/components/keystone-creator/keystone-creator.html +26 -0
  29. package/src/client/components/keystone-creator/keystone-creator.mts +229 -0
  30. package/src/client/constants.mts +76 -0
  31. package/src/client/custom.d.ts +11 -0
  32. package/src/client/dev-tools.mts +540 -0
  33. package/src/client/helpers.web.mts +178 -0
  34. package/src/client/index.html +147 -0
  35. package/src/client/index.mts +59 -0
  36. package/src/client/script.mts +13 -0
  37. package/src/client/style.css +605 -0
  38. package/src/client/types.mts +85 -0
  39. package/src/client/ui/shell/space-gib-shell-constants.mts +24 -0
  40. package/src/client/ui/shell/space-gib-shell-service.mts +233 -0
  41. package/src/client/ui/shell/space-gib-shell-types.mts +5 -0
  42. package/src/client/witness/app/space-gib/space-gib-app-v1.mts +160 -0
  43. package/src/client/witness/app/space-gib/space-gib-constants.mts +38 -0
  44. package/src/client/witness/app/space-gib/space-gib-helper.mts +72 -0
  45. package/src/client/witness/app/space-gib/space-gib-types.mts +47 -0
  46. package/src/common/keystone-policies.mts +159 -0
  47. package/src/respec-gib.node.mts +6 -0
  48. package/src/server/README.md +18 -0
  49. package/src/server/bootstrap-helper.mts +141 -0
  50. package/src/server/bootstrap-helper.respec.mts +100 -0
  51. package/src/server/metaspace-nodeindexedspace/metaspace-nodeindexedspace.mts +85 -0
  52. package/src/server/path-constants.mts +89 -0
  53. package/src/server/path-helper.mts +101 -0
  54. package/src/server/path-helper.respec.mts +94 -0
  55. package/src/server/serve-gib/CHANGELOG.md +29 -0
  56. package/src/server/serve-gib/README.md +34 -0
  57. package/src/server/serve-gib/constants.mts +1 -0
  58. package/src/server/serve-gib/handlers/api/debug/ws-echo.handler.mts +104 -0
  59. package/src/server/serve-gib/handlers/api/health.handler.mts +23 -0
  60. package/src/server/serve-gib/handlers/api/health.respec.mts +51 -0
  61. package/src/server/serve-gib/handlers/api/ibgib/ibgib-handler-types.mts +49 -0
  62. package/src/server/serve-gib/handlers/api/ibgib/ibgib.handler.mts +176 -0
  63. package/src/server/serve-gib/handlers/api/keystone/keystone-evolve.handler.mts +261 -0
  64. package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +146 -0
  65. package/src/server/serve-gib/handlers/api/keystone/keystone-get.handler.mts +198 -0
  66. package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +107 -0
  67. package/src/server/serve-gib/handlers/api/keystone/keystone-handler-types.mts +29 -0
  68. package/src/server/serve-gib/handlers/api/keystone/keystone-post.handler.mts +70 -0
  69. package/src/server/serve-gib/handlers/api/keystone/keystone-post.respec.mts +130 -0
  70. package/src/server/serve-gib/handlers/error-handler.mts +36 -0
  71. package/src/server/serve-gib/handlers/handler-base.mts +383 -0
  72. package/src/server/serve-gib/handlers/static-handler.mts +82 -0
  73. package/src/server/serve-gib/handlers/ws/sync-upgrade.handler.mts +498 -0
  74. package/src/server/serve-gib/handlers/ws/ws-helper.mts +111 -0
  75. package/src/server/serve-gib/handlers/ws/ws-types.mts +53 -0
  76. package/src/server/serve-gib/serve-gib-helpers.mts +32 -0
  77. package/src/server/serve-gib/serve-gib-v1.mts +172 -0
  78. package/src/server/serve-gib/serve-gib.respec.mts +90 -0
  79. package/src/server/serve-gib/types.mts +102 -0
  80. package/src/server/server-constants.mts +2 -0
  81. package/src/server/server.mts +96 -0
  82. package/tsconfig.json +29 -0
  83. package/tsconfig.server.json +29 -0
  84. package/tsconfig.test.json +27 -0
@@ -0,0 +1,261 @@
1
+ /**
2
+ * @module serve-gib/handlers/api/keystone/keystone-evolve.handler
3
+ *
4
+ * PUT /api/keystone/evolve/:domainAddr
5
+ *
6
+ * Receives an evolved domain keystone (`I^I1.Itjp`) and any related
7
+ * ibgibs (e.g., the `S^Stjp` session keystone).
8
+ *
9
+ * Validates the evolution, verifies the `S` session keystone, and persists
10
+ * them to the domain metaspace to authorize the sync session.
11
+ */
12
+
13
+ import { extractErrorMsg, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
14
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
15
+ import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
16
+ import { getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
17
+ import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
18
+
19
+ import { GLOBAL_LOG_A_LOT } from '../../../constants.mjs';
20
+ import { ServeGibHandlerWithMetaspaceBase } from '../../handler-base.mjs';
21
+ import { API_PATH_REGEXES } from '../../../../path-constants.mjs';
22
+ import { DomainInfo, ParamsWithDomain, RequestContext, ResponseResult } from '../../../types.mjs';
23
+ import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
24
+ import { KeystoneIbGib_V1 } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
25
+ import { IbGibSpaceAny } from '@ibgib/core-gib/dist/witness/space/space-base-v1.mjs';
26
+
27
+ const logalot = GLOBAL_LOG_A_LOT || true;
28
+
29
+ interface EvolveParams extends ParamsWithDomain {
30
+ domainAddr: string;
31
+ }
32
+
33
+ interface EvolveBody {
34
+ keystoneIbGib: KeystoneIbGib_V1;
35
+ relatedIbGibs?: IbGib_V1[];
36
+ }
37
+
38
+ export class KeystoneEvolveHandler extends ServeGibHandlerWithMetaspaceBase<EvolveParams, EvolveBody> {
39
+ protected override lc: string = `[${KeystoneEvolveHandler.name}]`;
40
+ protected override method: 'PUT' = 'PUT';
41
+ protected override regex = API_PATH_REGEXES.KEYSTONE_EVOLVE;
42
+
43
+ protected override async parseParamsImpl(reqCtx: RequestContext<EvolveParams, any>): Promise<EvolveParams | undefined> {
44
+ const match = reqCtx.pathname.match(this.regex);
45
+ if (match && match.length >= 3) {
46
+ const domainIb = decodeURIComponent(match[1]);
47
+ const domainGib = decodeURIComponent(match[2]);
48
+ const domainAddr = getIbGibAddr({ ib: domainIb, gib: domainGib });
49
+ return {
50
+ domainAddr,
51
+ domainInfo: this.getDomainInfo({ domainAddr })
52
+ };
53
+ }
54
+ return undefined;
55
+ }
56
+
57
+ protected override async validateQueryParams({ queryParams }: { queryParams: any; }): Promise<string[]> {
58
+ return []; // We don't expect any query parameters for evolution
59
+ }
60
+
61
+ protected override async handleRouteImpl(reqCtx: RequestContext<EvolveParams>): Promise<ResponseResult> {
62
+ const lc = `${this.lc}[handleRouteImpl]`;
63
+ try {
64
+ if (logalot) { console.log(`${lc} starting...`); }
65
+
66
+ if (!reqCtx.params) { return this.error(400, 'Invalid parameters'); }
67
+ if (!reqCtx.metaspace) { return this.error(500, 'Metaspace not initialized'); }
68
+
69
+ const { domainAddr } = reqCtx.params;
70
+ const bodyStr = reqCtx.body;
71
+ if (!bodyStr) { return this.error(400, 'Empty body'); }
72
+
73
+ let parsed: EvolveBody;
74
+ try {
75
+ parsed = JSON.parse(bodyStr);
76
+ } catch (err) {
77
+ return this.error(400, 'Invalid request JSON body: bodyStr does NOT parse. (E: 2f81c8c80a6884ff32b877671e92f326)');
78
+ }
79
+
80
+ const { keystoneIbGib, relatedIbGibs = [] } = parsed;
81
+ if (!keystoneIbGib) {
82
+ return this.error(400, 'Invalid request. Body must contain keystoneIbGib (E: e234647f79282982c8012fe8015f5826)');
83
+ }
84
+
85
+ const addr = getIbGibAddr({ ibGib: keystoneIbGib });
86
+
87
+ // 1. Intrinsic hash validation
88
+ const intrinsicErrors = await validateIbGibIntrinsically({ ibGib: keystoneIbGib });
89
+ if (intrinsicErrors && intrinsicErrors.length > 0) {
90
+ return this.error(422, 'Intrinsic keystone validation failed. (E: 6efeb756cb0fb6c3187c8448c5035826)', { errors: intrinsicErrors });
91
+ }
92
+
93
+ // 2. Verify URL :domainAddr matches the keystone's tjp
94
+ const { tjpGib } = getGibInfo({ ibGibAddr: addr });
95
+ const expectedTjpGib = getGibInfo({ ibGibAddr: domainAddr }).punctiliarHash;
96
+ if (tjpGib !== expectedTjpGib) {
97
+ return this.error(422, `Keystone tjpGib (${tjpGib}) does not match URL domainAddr tjpGib (${expectedTjpGib}) (E: 703d3c64b7382f21f863c1e83533c826)`);
98
+ }
99
+
100
+ // 3. Load previous frame (the current tip in metaspace)
101
+ const space = await reqCtx.metaspace.getLocalUserSpace({ lock: false });
102
+ if (!space) {
103
+ return this.error(500, 'No local user space found (E: a1144e5d6cd9641fb82d6b359f131126)');
104
+ }
105
+ const keystoneService = new KeystoneService_V1();
106
+ let prevIbGib: KeystoneIbGib_V1;
107
+ try {
108
+ prevIbGib = await keystoneService.getLatestKeystone({
109
+ addr: domainAddr,
110
+ metaspace: reqCtx.metaspace,
111
+ space
112
+ });
113
+ } catch (error) {
114
+ return this.error(422, `Could not retrieve previous keystone tip for ${domainAddr} (E: bc2da8fb4dbda2c65851de9dd0b0eb26)`, { error: extractErrorMsg(error) });
115
+ }
116
+
117
+ // 4. Validate Evolution Transition
118
+ const validationErrors = await keystoneService.validate({
119
+ currentIbGib: keystoneIbGib,
120
+ prevIbGib
121
+ });
122
+ if (validationErrors && validationErrors.length > 0) {
123
+ return this.error(422, 'Keystone evolution validation failed', { errors: validationErrors });
124
+ }
125
+
126
+ // 5. Check Single Pool Restriction
127
+ const restrictionError = this.validateSinglePoolRestriction(keystoneIbGib);
128
+ if (restrictionError) {
129
+ return this.error(422, restrictionError); /* <<<< returns early */
130
+ }
131
+
132
+ // 6. Route by claim verb
133
+ const proofs = keystoneIbGib.data?.proofs ?? [];
134
+ if (proofs.length !== 1) { throw new Error(`(UNEXPECTED) evolving keystone with proofs.length !== 1? we just checked that the length of proofs was exactly 1 in checkSinglePoolRestriction (E: 3021bb0ce9783e27c8e3492820aeb926)`); }
135
+ const { claim } = proofs[0];
136
+ if (!claim) { throw new Error(`proof.claim is falsy (E: e1dd983db341185528a75768919ea826)`); }
137
+ const { verb } = claim;
138
+ if (!verb) { throw new Error(`(UNEXPECTED) claim.verb falsy? atow (05/15/2026) I thought all claims should have a verb. I could be wrong of course as it's still early days. (E: 933a9856d6625b547d7ec158d678a826)`); }
139
+
140
+ switch (verb) {
141
+ case 'sync':
142
+ return await this.handleSyncEvolution(reqCtx, keystoneIbGib, relatedIbGibs, space);
143
+ default:
144
+ throw new Error(`non-sync keystone evolution not yet implemented (E: 83cb29bf67d681542bfd486d0f91e726)`);
145
+ // return await this.handleDefaultEvolution(reqCtx, keystoneIbGib, space);
146
+ }
147
+ } catch (error) {
148
+ console.error(`${lc} ${extractErrorMsg(error)}`);
149
+ return this.error(500, `Internal error evolving keystone`, { details: extractErrorMsg(error) });
150
+ } finally {
151
+ if (logalot) { console.log(`${lc} complete.`); }
152
+ }
153
+ }
154
+
155
+ protected validateSinglePoolRestriction(keystoneIbGib: KeystoneIbGib_V1): string | undefined {
156
+ const proofs = keystoneIbGib.data?.proofs ?? [];
157
+ if (proofs.length === 0) {
158
+ return 'No proofs found in evolved keystone data';
159
+ }
160
+
161
+ const poolIds = new Set<string>();
162
+ for (const proof of proofs) {
163
+ for (const solution of proof.solutions || []) {
164
+ if (solution.poolId) {
165
+ poolIds.add(solution.poolId);
166
+ }
167
+ }
168
+ }
169
+
170
+ if (poolIds.size > 1) {
171
+ return `Multiple pools evolved (${Array.from(poolIds).join(', ')}). Only one pool evolution allowed at a time.`;
172
+ }
173
+
174
+ return undefined; // no error
175
+ }
176
+
177
+ protected async handleSyncEvolution(
178
+ reqCtx: RequestContext<EvolveParams>,
179
+ keystoneIbGib: KeystoneIbGib_V1,
180
+ relatedIbGibs: IbGib_V1[],
181
+ space: IbGibSpaceAny
182
+ ): Promise<ResponseResult> {
183
+ const lc = `${this.lc}[${this.handleSyncEvolution.name}]`;
184
+ try {
185
+ if (logalot) { console.log(`${lc} starting... (I: 80cf7ba10a781fad87171b747de10826)`); }
186
+
187
+ if (logalot) { console.log(`${lc} starting...`); }
188
+
189
+ const proofs = keystoneIbGib.data?.proofs ?? [];
190
+ if (proofs.length !== 1) { throw new Error(`(UNEXPECTED) proofs.length !== 1? (E: ac9748a187f87455bc01fdeeb4363826)`); }
191
+ const claim = proofs[0].claim;
192
+ const sAddr = claim?.target;
193
+ if (!sAddr) {
194
+ return this.error(422, 'No target address found in sync claim');
195
+ }
196
+
197
+ // Validate S^Stjp (session keystone ibgib) from relatedIbGibs
198
+ const sIbGib = relatedIbGibs.find(ibGib => getIbGibAddr({ ibGib }) === sAddr) as KeystoneIbGib_V1 | undefined;
199
+ if (!sIbGib) {
200
+ return this.error(422, `Session keystone ${sAddr} must be provided in relatedIbGibs`);
201
+ }
202
+
203
+ const sIntrinsicErrors = await validateIbGibIntrinsically({ ibGib: sIbGib });
204
+ if (sIntrinsicErrors && sIntrinsicErrors.length > 0) {
205
+ return this.error(422, 'Intrinsic validation failed for S^Stjp', { errors: sIntrinsicErrors });
206
+ }
207
+
208
+ const keystoneService = new KeystoneService_V1();
209
+ const sGenesisErrors = await keystoneService.validateGenesisKeystone({ keystoneIbGib: sIbGib });
210
+ if (sGenesisErrors && sGenesisErrors.length > 0) {
211
+ return this.error(422, 'Genesis validation failed for S^Stjp', { errors: sGenesisErrors });
212
+ }
213
+
214
+ // Persist both I and S
215
+ await reqCtx.metaspace!.put({ ibGibs: [keystoneIbGib, sIbGib], space });
216
+ await reqCtx.metaspace!.registerNewIbGib({ ibGib: keystoneIbGib, space });
217
+ await reqCtx.metaspace!.registerNewIbGib({ ibGib: sIbGib, space });
218
+
219
+ const addr = getIbGibAddr({ ibGib: keystoneIbGib });
220
+ if (logalot) { console.log(`${lc} domain evolution stored and registered: ${addr}`); }
221
+
222
+ return this.ok({
223
+ message: `Successfully evolved domain keystone with session keystone`,
224
+ });
225
+
226
+ } catch (error) {
227
+ console.error(`${lc} ${extractErrorMsg(error)}`);
228
+ throw error;
229
+ } finally {
230
+ if (logalot) { console.log(`${lc} complete.`); }
231
+ }
232
+ }
233
+
234
+ protected async handleDefaultEvolution(
235
+ reqCtx: RequestContext<EvolveParams>,
236
+ keystoneIbGib: KeystoneIbGib_V1,
237
+ space: IbGibSpaceAny
238
+ ): Promise<ResponseResult> {
239
+ const lc = `${this.lc}[${this.handleDefaultEvolution.name}]`;
240
+ try {
241
+ if (logalot) { console.log(`${lc} starting... (I: 8b755b4087b16c0d760d3ca397fad726)`); }
242
+
243
+ // Persist only I
244
+ await reqCtx.metaspace!.put({ ibGibs: [keystoneIbGib], space });
245
+ await reqCtx.metaspace!.registerNewIbGib({ ibGib: keystoneIbGib, space });
246
+
247
+ const addr = getIbGibAddr({ ibGib: keystoneIbGib });
248
+ if (logalot) { console.log(`${lc} domain evolution stored and registered: ${addr} (I: 990d686a19d645afaa624e386caac826)`); }
249
+
250
+ return this.ok({
251
+ message: `Successfully evolved domain keystone. (I: 4cd058a08a171069982a7f3808cbe826)`,
252
+ });
253
+ } catch (error) {
254
+ console.error(`${lc} ${extractErrorMsg(error)}`);
255
+ throw error;
256
+ } finally {
257
+ if (logalot) { console.log(`${lc} complete.`); }
258
+ }
259
+ }
260
+
261
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @module serve-gib/handlers/api/keystone/keystone-genesis.handler
3
+ *
4
+ * POST /api/keystone/genesis
5
+ *
6
+ * Accepts a single genesis-frame keystone ibgib from the client and
7
+ * bootstraps a new domain-isolated metaspace for it.
8
+ *
9
+ * ## request body
10
+ *
11
+ * ```json
12
+ * { "keystoneIbGib": <KeystoneIbGib_V1> }
13
+ * ```
14
+ *
15
+ * ## validation steps
16
+ *
17
+ * 1. Body contains `keystoneIbGib` (single ibgib, not an array).
18
+ * 2. Intrinsic hash validation passes (`validateIbGibIntrinsically`).
19
+ * 3. Structural keystone check: `ib` starts with `"keystone"`, no `past` in
20
+ * `rel8ns` (genesis = TJP frame, no prior timeline).
21
+ * 4. Domain uniqueness: the path derived from the keystone's tjpGib must not
22
+ * already exist on disk — if it does, return 409 Conflict.
23
+ * 5. Bootstrap: call `bootstrapDomainMetaspace` to create folders + metaspace.
24
+ * 6. Persist the keystone ibgib into the newly created domain space.
25
+ *
26
+ * ## notes
27
+ *
28
+ * Unlike domain-scoped handlers, this handler does NOT rely on
29
+ * `reqCtx.metaspace` (which is undefined when no domain exists yet). It
30
+ * creates the metaspace itself using `bootstrapDomainMetaspace`.
31
+ */
32
+
33
+ import { existsSync } from 'node:fs';
34
+
35
+ import { extractErrorMsg, pretty } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
36
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
37
+ import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
38
+ import { getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
39
+ import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
40
+
41
+ import { GLOBAL_LOG_A_LOT } from '../../../constants.mjs';
42
+ import { ServeGibHandlerBase } from '../../handler-base.mjs';
43
+ import { RequestContext, ResponseResult, ServeGibHttpMethod } from '../../../types.mjs';
44
+ import { API_PATH_REGEXES } from '../../../../path-constants.mjs';
45
+ import { getDomainRootPath } from '../../../../bootstrap-helper.mjs';
46
+ import { bootstrapDomainMetaspace } from '../../../../bootstrap-helper.mjs';
47
+
48
+ const logalot = GLOBAL_LOG_A_LOT || true;
49
+
50
+
51
+
52
+
53
+ /**
54
+ * POST /api/keystone/genesis
55
+ *
56
+ * No path params — the domain is derived from the incoming keystone ibgib.
57
+ */
58
+ export class KeystoneGenesisHandler extends ServeGibHandlerBase<undefined, undefined> {
59
+ protected override lc: string = `[${KeystoneGenesisHandler.name}]`;
60
+ protected override method: ServeGibHttpMethod = 'POST';
61
+ protected override regex = API_PATH_REGEXES.KEYSTONE_GENESIS;
62
+
63
+ protected async handleRouteImpl(
64
+ reqCtx: RequestContext<undefined, undefined>
65
+ ): Promise<ResponseResult | undefined> {
66
+ const lc = `${this.lc}[${this.handleRouteImpl.name}]`;
67
+ try {
68
+ if (logalot) { console.log(`${lc} starting... (I: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c526)`); }
69
+
70
+ // ----------------------------------------------------------------
71
+ // 1. Parse body — expect { keystoneIbGib }
72
+ // ----------------------------------------------------------------
73
+ let parsed: any;
74
+ try {
75
+ parsed = JSON.parse(reqCtx.body);
76
+ } catch {
77
+ return this.error(400, 'Request body must be valid JSON');
78
+ }
79
+
80
+ const { keystoneIbGib } = parsed;
81
+ if (!keystoneIbGib) {
82
+ return this.error(400, 'Body must contain keystoneIbGib');
83
+ }
84
+
85
+ // ----------------------------------------------------------------
86
+ // 2. Intrinsic hash validation
87
+ // ----------------------------------------------------------------
88
+ const intrinsicErrors = await validateIbGibIntrinsically({ ibGib: keystoneIbGib });
89
+ if (intrinsicErrors) {
90
+ return this.error(422, 'Intrinsic keystone validation failed', { errors: intrinsicErrors });
91
+ }
92
+
93
+ // ----------------------------------------------------------------
94
+ // 3. Structural keystone + genesis-frame checks
95
+ // ----------------------------------------------------------------
96
+ const keystoneService = new KeystoneService_V1();
97
+ const keystoneErrors = await keystoneService.validateGenesisKeystone({
98
+ keystoneIbGib
99
+ });
100
+ if (keystoneErrors && keystoneErrors.length > 0) {
101
+ return this.error(422, 'Keystone genesis validation failed', { errors: keystoneErrors });
102
+ }
103
+
104
+ // ----------------------------------------------------------------
105
+ // 4. Domain uniqueness check — 409 if the domain already exists
106
+ // ----------------------------------------------------------------
107
+ const addr = getIbGibAddr({ ibGib: keystoneIbGib });
108
+ const { tjpGib, punctiliarHash } = getGibInfo({ ibGibAddr: addr });
109
+ const domainGib = tjpGib || punctiliarHash;
110
+ if (!domainGib) {
111
+ return this.error(422, `Could not extract domain identifier from keystone address: ${addr}`);
112
+ }
113
+
114
+ const domainRootPath = getDomainRootPath(domainGib, reqCtx.dataDir);
115
+ if (existsSync(domainRootPath)) {
116
+ return this.error(409, `Domain already exists for this keystone (tjpGib: ${domainGib}). Use PUT /api/keystone/evolve/:domainAddr to evolve.`);
117
+ }
118
+
119
+ // ----------------------------------------------------------------
120
+ // 5. Bootstrap the new domain metaspace (creates folders)
121
+ // ----------------------------------------------------------------
122
+ const metaspace = await bootstrapDomainMetaspace(addr, reqCtx.dataDir);
123
+
124
+ // ----------------------------------------------------------------
125
+ // 6. Persist the genesis keystone into the new domain space
126
+ // ----------------------------------------------------------------
127
+ const space = await metaspace.getLocalUserSpace({ lock: false });
128
+ if (!space) {
129
+ throw new Error(`(UNEXPECTED) Could not get local user space after bootstrapping domain for ${addr} (E: b1c2d3e4f5a6b7c8d9e0f1a2b3c4d526)`);
130
+ }
131
+
132
+ await metaspace.put({ ibGibs: [keystoneIbGib], space });
133
+ await metaspace.registerNewIbGib({ ibGib: keystoneIbGib, space });
134
+
135
+ console.log(`${lc} domain created and keystone persisted: ${addr}`);
136
+ if (logalot) { console.log(`${lc} keystoneIbGib: ${pretty(keystoneIbGib)} (I: d598c6ff7a48997d585b84c19c464826)`); }
137
+ return this.ok({ success: true, addr }, 201);
138
+ } catch (error) {
139
+ const emsg = extractErrorMsg(error);
140
+ console.error(`${lc} ${emsg}`);
141
+ return this.error(500, emsg);
142
+ } finally {
143
+ if (logalot) { console.log(`${lc} complete.`); }
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @module serve-gib/handlers/api/keystone
3
+ */
4
+
5
+ import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
6
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
7
+ import { IbGib_V1 } from '@ibgib/ts-gib/dist/V1/types.mjs';
8
+ import { getGibInfo } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
9
+ import { IbGibAddr } from '@ibgib/ts-gib/dist/types.mjs';
10
+ import { KeystoneService_V1 } from '@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs';
11
+ import { validateKeystoneGraph } from '@ibgib/core-gib/dist/keystone/keystone-helpers.mjs';
12
+ import { KeystoneIbGib_V1 } from '@ibgib/core-gib/dist/keystone/keystone-types.mjs';
13
+ import { IbGibSpaceResultData, IbGibSpaceResultIbGib, IbGibSpaceResultRel8ns } from '@ibgib/core-gib/dist/witness/space/space-types.mjs';
14
+
15
+ import { GLOBAL_LOG_A_LOT } from '../../../constants.mjs';
16
+ import { ServeGibHandlerWithMetaspaceBase } from '../../handler-base.mjs';
17
+ import { RequestContext, ResponseResult, ServeGibHttpMethod } from '../../../types.mjs';
18
+ import { API_PATH_REGEXES } from '../../../../path-constants.mjs';
19
+ import {
20
+ DEFAULT_QUERY_PARAMS_GET_KEYSTONE, isKnownQueryParamsKey_KeystoneGet,
21
+ KeystoneGetQueryParams, KeystoneParams, KNOWN_QUERY_PARAMS_KEYSTONE_GET
22
+ } from './keystone-handler-types.mjs';
23
+ import { validateAndCoerceBooleanQueryParam } from '../../../serve-gib-helpers.mjs';
24
+
25
+ export interface KeystoneGetResponseBody {
26
+ domainGraph: Record<string, KeystoneIbGib_V1>;
27
+ }
28
+
29
+ const logalot = GLOBAL_LOG_A_LOT;
30
+
31
+ /**
32
+ * GET /api/keystone/:domainAddr
33
+ * GET /api/keystone/:domainAddr&getLatest=true
34
+ */
35
+ export class KeystoneGetHandler extends ServeGibHandlerWithMetaspaceBase<KeystoneParams, KeystoneGetQueryParams> {
36
+ protected override lc: string = `[${KeystoneGetHandler.name}]`;
37
+ protected override method: ServeGibHttpMethod = 'GET';
38
+ protected override regex = API_PATH_REGEXES.KEYSTONE_ADDR;
39
+
40
+ protected override async parseParamsImpl(reqCtx: RequestContext<KeystoneParams, KeystoneGetQueryParams>): Promise<KeystoneParams | undefined> {
41
+ const lc = `${this.lc}[${this.parseParamsImpl.name}]`;
42
+ try {
43
+ if (logalot) { console.log(`${lc} starting...`); }
44
+ const match = reqCtx.pathname.match(this.regex);
45
+ if (!match) { throw new Error(`(UNEXPECTED) match falsy for regex? at this point, we have passed canHandleRoute, so this should parse correctly. (E: 02c868316604c7c26823380885b79c26)`); }
46
+
47
+ const domainIb = decodeURIComponent(match[1]);
48
+ const domainGib = decodeURIComponent(match[2]);
49
+ const domainAddr = getIbGibAddr({ ib: domainIb, gib: domainGib });
50
+ return {
51
+ domainInfo: this.getDomainInfo({ domainAddr }),
52
+ };
53
+ } catch (error) {
54
+ console.error(`${lc} ${extractErrorMsg(error)}`);
55
+ throw error;
56
+ } finally {
57
+ if (logalot) { console.log(`${lc} complete.`); }
58
+ }
59
+ }
60
+
61
+ protected override async validateQueryParams({ queryParams }: { queryParams: any; }): Promise<string[]> {
62
+ const lc = `${this.lc}[${this.validateQueryParams.name}]`;
63
+ try {
64
+ if (logalot) { console.log(`${lc} starting... (I: f619e8121988498cd8e8928986663826)`); }
65
+
66
+ const errors: string[] = [];
67
+
68
+ // strict checking
69
+ const queryParamsKeys = Object.keys(queryParams);
70
+ const invalidKeys: string[] = queryParamsKeys.filter(x => !isKnownQueryParamsKey_KeystoneGet(x));
71
+ if (invalidKeys.length > 0) {
72
+ throw new Error(`invalid query params keys: ${invalidKeys.join(', ')}. valid query params keys: ${KNOWN_QUERY_PARAMS_KEYSTONE_GET.join(', ')} (E: f9642823b568a1cfe89e0401567ac826)`);
73
+ }
74
+
75
+ // Enforce that EVERY key must have a validation function mapping
76
+ type ValidationFn = () => Promise<void>;
77
+ const ValidationFns: Record<KNOWN_QUERY_PARAMS_KEYSTONE_GET, ValidationFn> = {
78
+ getLatest: async () => {
79
+ validateAndCoerceBooleanQueryParam(queryParams, 'getLatest', errors);
80
+ },
81
+ getGraph: async () => {
82
+ validateAndCoerceBooleanQueryParam(queryParams, 'getGraph', errors);
83
+ },
84
+ };
85
+
86
+ // execute all validation functions
87
+ for (const fn of Object.values(ValidationFns)) {
88
+ await fn();
89
+ }
90
+
91
+ return errors;
92
+
93
+ } catch (error) {
94
+ console.error(`${lc} ${extractErrorMsg(error)}`);
95
+ throw error;
96
+ } finally {
97
+ if (logalot) { console.log(`${lc} complete.`); }
98
+ }
99
+ }
100
+
101
+ protected async handleRouteImpl(reqCtx: RequestContext<KeystoneParams, KeystoneGetQueryParams>): Promise<ResponseResult | undefined> {
102
+ const lc = `[${this.handleRouteImpl.name}]`;
103
+ try {
104
+ if (logalot) { console.log(`${lc} starting...`); }
105
+
106
+ const { metaspace } = reqCtx;
107
+ if (!metaspace) { throw new Error(`(UNEXPECTED) reqCtx.metaspace falsy? (E: 871a396f7e824989981a1508d0d17826)`); }
108
+
109
+ const { domainInfo } = reqCtx.params as KeystoneParams;
110
+ if (!domainInfo) { throw new Error(`(UNEXPECTED) reqCtx.params.domainInfo falsy? at this point, we've passed canHandleRoute so this should be truthy (E: 5d3e96d9f8d8f65258d530af0e223826)`); }
111
+ const { addr: domainAddr } = domainInfo;
112
+
113
+ // if (!domainIb || !domainGib) { return this.error(400, 'Keystone address required'); }
114
+ // const addr = `${domainIb}^${domainGib}`;
115
+
116
+ if (logalot) { console.log(`${lc} domainAddr: ${domainAddr} (I: ea08786359e85481a89c77d84e8f0826)`); }
117
+
118
+ // there are two paths for this: getLatest or not (get exact given addr)
119
+ // this should be driven by queryParams
120
+
121
+ const queryParams = {
122
+ ...DEFAULT_QUERY_PARAMS_GET_KEYSTONE,
123
+ ...(reqCtx.queryParams ?? {}),
124
+ }
125
+
126
+ const { getLatest, getGraph } = queryParams;
127
+
128
+ if (!getLatest) { throw new Error(`"getLatest: false" not implemented yet. currently this always returns the graph. (E: befd28156438a0b538cf107872e89b26)`); }
129
+ if (!getGraph) { throw new Error(`"getGraph: false" not implemented yet. currently this always returns the graph. (E: 84c8a88fdb28e72d884addb8f43fc826)`); }
130
+
131
+ /**
132
+ * get the default local user space that corresponds to the current
133
+ * keystone domain (which is what the metaspace was initialized
134
+ * with).
135
+ */
136
+ const space = await metaspace.getLocalUserSpace({ lock: false });
137
+ if (!space) { throw new Error(`(UNEXPECTED) space falsy? we couldn't get the default local userspace from the metaspace for the addr (${domainAddr}) (E: 934551f1845f94c8320a8a980025c526)`); }
138
+
139
+
140
+ // use this code later if we de
141
+ let domainAddr_latest: IbGibAddr = domainAddr;
142
+ if (getLatest) {
143
+ const latestAddr = await metaspace.getLatestAddr({ addr: domainAddr, space });
144
+ if (latestAddr) { domainAddr_latest = latestAddr; }
145
+ }
146
+
147
+
148
+ const domainGraph = await metaspace.getDependencyGraph({
149
+ ibGibAddr: domainAddr_latest,
150
+ /**
151
+ * since domain is a keystone, "live" is not needed because the
152
+ * entire graph is composed of stones with no timelines.
153
+ */
154
+ live: false,
155
+ space,
156
+ });
157
+
158
+ let keystoneIbGib: KeystoneIbGib_V1 = domainGraph[domainAddr_latest] as KeystoneIbGib_V1;
159
+ if (!keystoneIbGib) {
160
+ const errorMsg_addrInfo = domainAddr_latest === domainAddr ?
161
+ `domainAddr: ${domainAddr}` :
162
+ `domainAddr: ${domainAddr}, domainAddr_latest: `
163
+ return this.error(404, `Keystone not found. ${errorMsg_addrInfo} (E: 0238c85f94889607088220a324db1826)`); /* <<<< returns early */
164
+ }
165
+
166
+ // go ahead and validate even though we just pulled this (should be
167
+ // valid or it shouldn't have been persisted)
168
+
169
+ const keystoneValidationErrors = await validateKeystoneGraph({
170
+ keystoneIbGib,
171
+ getLatest,
172
+ invalidIfMoreRecentKeystoneFoundInSpace: getLatest,
173
+ space,
174
+ });
175
+
176
+ if (keystoneValidationErrors.length > 0) {
177
+ const errorMsg_addrInfo = domainAddr_latest === domainAddr ?
178
+ `domainAddr: ${domainAddr}` :
179
+ `domainAddr: ${domainAddr}, domainAddr_latest: `
180
+
181
+ return this.error(404, `Keystone invalid for ${errorMsg_addrInfo}. validationErrors: ${keystoneValidationErrors.join('|')}`); /* <<<< returns early */
182
+ }
183
+
184
+ // valid keystone graph
185
+ const responseBody: KeystoneGetResponseBody = {
186
+ domainGraph: domainGraph as Record<string, KeystoneIbGib_V1>,
187
+ };
188
+ return this.ok(responseBody);
189
+
190
+ } catch (error) {
191
+ const emsg = `${lc} ${extractErrorMsg(error)}`;
192
+ console.error(emsg);
193
+ return this.error(500, emsg);
194
+ } finally {
195
+ if (logalot) { console.log(`${lc} complete.`); }
196
+ }
197
+ }
198
+ }