githolon 0.100.2 → 0.100.4

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/cli.mjs CHANGED
@@ -3098,6 +3098,7 @@ __export(governance_exports, {
3098
3098
  certifyEra: () => certifyEra,
3099
3099
  consentRebirth: () => consentRebirth,
3100
3100
  discoverSigningDomain: () => discoverSigningDomain,
3101
+ ensureApplicationWorkspace: () => ensureApplicationWorkspace,
3101
3102
  ensureSovereignHome: () => ensureSovereignHome,
3102
3103
  exchangeOnboardingToken: () => exchangeOnboardingToken,
3103
3104
  executeRepairRebirth: () => executeRepairRebirth,
@@ -3120,6 +3121,7 @@ __export(governance_exports, {
3120
3121
  renderRebirthImpact: () => renderRebirthImpact,
3121
3122
  resolveGovernanceHash: () => resolveGovernanceHash,
3122
3123
  resolveGovernancePrincipal: () => resolveGovernancePrincipal,
3124
+ signAndPostChildLifecycle: () => signAndPostChildLifecycle,
3123
3125
  signAndPostGovernance: () => signAndPostGovernance,
3124
3126
  signAndPostOffer: () => signAndPostOffer,
3125
3127
  signAndPostWorkspaceBirth: () => signAndPostWorkspaceBirth,
@@ -3276,6 +3278,20 @@ async function exchangeOnboardingToken(session, keyHash, idp = process.env["NOMO
3276
3278
  }
3277
3279
  return { token: body.token, issuer: body.issuer, subject: body.subject };
3278
3280
  }
3281
+ async function workspaceMainHead(cloud, workspace) {
3282
+ return fetch(
3283
+ `${cloud}/v2/workspaces/${encodeURIComponent(workspace)}/git/info/refs?service=git-upload-pack`
3284
+ ).then(async (r) => r.ok ? /([0-9a-f]{40}) refs\/heads\/main\b/.exec(await r.text())?.[1] : void 0).catch(() => void 0);
3285
+ }
3286
+ async function waitForWorkspaceMain(cloud, workspace, timeoutMs = 3e4) {
3287
+ const deadline = Date.now() + timeoutMs;
3288
+ while (Date.now() < deadline) {
3289
+ const head = await workspaceMainHead(cloud, workspace);
3290
+ if (head !== void 0) return head;
3291
+ await new Promise((resolve6) => setTimeout(resolve6, 250));
3292
+ }
3293
+ return void 0;
3294
+ }
3279
3295
  async function ensureSovereignHome(opts = {}) {
3280
3296
  const cloud = cloudBase(opts.cloud);
3281
3297
  const uid = opts.principal ?? currentPrincipal();
@@ -3420,6 +3436,123 @@ async function ensureSovereignHome(opts = {}) {
3420
3436
  }
3421
3437
  return { home, principal: actor, device, created: !exists };
3422
3438
  }
3439
+ async function ensureApplicationWorkspace(application, opts = {}) {
3440
+ const cloud = cloudBase(opts.cloud);
3441
+ const statusUrl = `${cloud}/v2/workspaces/${encodeURIComponent(application)}`;
3442
+ const current = await fetch(statusUrl).catch(() => null);
3443
+ if (current?.ok) {
3444
+ const status2 = await current.json().catch(() => null);
3445
+ const principal = String(status2?.lineage?.principal ?? "");
3446
+ const session = canonicalPrincipal(currentPrincipal() ?? "");
3447
+ if (await waitForWorkspaceMain(cloud, application) === void 0) {
3448
+ throw new Error(`application '${application}' is born but canonical ledger main did not become readable`);
3449
+ }
3450
+ return {
3451
+ application,
3452
+ parent: String(status2?.lineage?.parent ?? ""),
3453
+ principal,
3454
+ created: false,
3455
+ ...principal !== "" && session !== "" && sameIdentity(principal, session) ? { deployPrincipal: principal } : {}
3456
+ };
3457
+ }
3458
+ if (current !== null && current.status !== 404 && current.status !== 409) {
3459
+ throw new Error(`could not inspect deploy target '${application}' (${current.status}); refusing to guess that it is unborn`);
3460
+ }
3461
+ const home = await ensureSovereignHome(opts);
3462
+ if (application === home.home) {
3463
+ return {
3464
+ application,
3465
+ parent: "root",
3466
+ principal: home.principal,
3467
+ created: home.created,
3468
+ deployPrincipal: home.principal
3469
+ };
3470
+ }
3471
+ if (await fetch(statusUrl).then((r) => r.ok).catch(() => false)) {
3472
+ if (await waitForWorkspaceMain(cloud, application) === void 0) {
3473
+ throw new Error(`application '${application}' is born but canonical ledger main did not become readable`);
3474
+ }
3475
+ return {
3476
+ application,
3477
+ parent: home.home,
3478
+ principal: home.principal,
3479
+ created: false,
3480
+ deployPrincipal: home.principal
3481
+ };
3482
+ }
3483
+ const domains = await fetch(`${cloud}/v2/workspaces/${encodeURIComponent(home.home)}/domains`).then(async (r) => {
3484
+ if (!r.ok) throw new Error(`application parent law discovery failed (${r.status})`);
3485
+ return await r.json();
3486
+ });
3487
+ const frameworkHash = domains.currentLaw?.["nomos"];
3488
+ const lawHash = domains.currentLaw?.["workspaces"];
3489
+ if (!frameworkHash || !lawHash) {
3490
+ throw new Error(`the signed session home does not carry the framework law needed to birth '${application}'`);
3491
+ }
3492
+ const parent = await connectGovernance(cloud, home.home);
3493
+ const birthCert = await parent.certSign({
3494
+ child: application,
3495
+ founder: home.principal,
3496
+ verified: true,
3497
+ parentKey: home.device.public,
3498
+ parentId: home.home,
3499
+ epoch: 0,
3500
+ secret: home.device.secret
3501
+ });
3502
+ const bare = lineageIdentity(home.principal);
3503
+ const now = Date.now();
3504
+ const birth = await fetch(
3505
+ `${cloud}/v2/workspaces/${encodeURIComponent(home.home)}/workspaces/birthChild`,
3506
+ {
3507
+ method: "POST",
3508
+ headers: { "content-type": "application/json" },
3509
+ body: JSON.stringify({
3510
+ name: application,
3511
+ parent: home.home,
3512
+ principal: bare,
3513
+ tier: "edge",
3514
+ requestedBy: bare,
3515
+ requestedAt: now,
3516
+ foundingAdminHash: home.device.keyHash,
3517
+ bornAt: new Date(now).toISOString(),
3518
+ frameworkHash,
3519
+ lawHash,
3520
+ platformCert: JSON.stringify(birthCert),
3521
+ actor: home.principal,
3522
+ authorSecret: home.device.secret
3523
+ })
3524
+ }
3525
+ );
3526
+ const body = await birth.json().catch(() => null);
3527
+ const failed = body?.birthOutcomes?.find((outcome) => outcome.status !== "born");
3528
+ if (!birth.ok || body?.ok === false || failed) {
3529
+ if (!await fetch(statusUrl).then((r) => r.ok).catch(() => false)) {
3530
+ throw new Error(
3531
+ `Nomos could not create application '${application}' (${birth.status}): ${failed?.reason ?? String(body?.error ?? JSON.stringify(body))}`
3532
+ );
3533
+ }
3534
+ }
3535
+ if (await waitForWorkspaceMain(cloud, application) === void 0) {
3536
+ throw new Error(`Nomos admitted application '${application}' but canonical ledger main did not become readable`);
3537
+ }
3538
+ const admin = await signAndPostOffer(
3539
+ application,
3540
+ "workspaces",
3541
+ "grantAdmin",
3542
+ { principal: bare, label: "application founder", grantedBy: bare, grantedAt: now },
3543
+ { ...opts, principal: bare }
3544
+ );
3545
+ if (!admin.ok) {
3546
+ throw new Error(`application '${application}' refused its founding admin (${admin.status}): ${JSON.stringify(admin.body)}`);
3547
+ }
3548
+ return {
3549
+ application,
3550
+ parent: home.home,
3551
+ principal: home.principal,
3552
+ created: true,
3553
+ deployPrincipal: home.principal
3554
+ };
3555
+ }
3423
3556
  async function signAndPostWorkspaceRetirement(parent, workspaceId, creator, opts) {
3424
3557
  return signAndPostPreparedOffer(
3425
3558
  parent,
@@ -3433,6 +3566,22 @@ async function signAndPostWorkspaceRetirement(parent, workspaceId, creator, opts
3433
3566
  creator
3434
3567
  );
3435
3568
  }
3569
+ async function signAndPostChildLifecycle(parent, child, creator, action, opts) {
3570
+ return signAndPostPreparedOffer(
3571
+ parent,
3572
+ void 0,
3573
+ (actor) => action === "retire" ? {
3574
+ directiveId: "retireChild",
3575
+ payload: { child, principal: creator, retiredBy: lineageIdentity(actor), retiredAt: Date.now() }
3576
+ } : {
3577
+ directiveId: "reclaimChild",
3578
+ payload: { child, reclaimedBy: lineageIdentity(actor), reclaimedAt: Date.now() }
3579
+ },
3580
+ opts,
3581
+ "self-if-unenrolled",
3582
+ creator
3583
+ );
3584
+ }
3436
3585
  async function relaySealedOfferViaStage(cloud, ws, directiveId, intentBytesB64, fetchImpl) {
3437
3586
  const raw = Buffer.from(intentBytesB64, "base64");
3438
3587
  const sha2562 = createHash("sha256").update(raw).digest("hex");
@@ -5471,9 +5620,19 @@ async function wsStatus(name, opts) {
5471
5620
  out4(text);
5472
5621
  return 0;
5473
5622
  }
5623
+ async function childBirthRecord(cloud, parent, child) {
5624
+ try {
5625
+ const id = `nomos-birth-record:${child}`;
5626
+ const r = await fetch(`${cloud}/v2/workspaces/${encodeURIComponent(parent)}/aggregates/${encodeURIComponent(id)}`);
5627
+ const body = await r.json().catch(() => void 0);
5628
+ return body?.ok === true ? body.rows?.find((row) => row.data?.child === child) : void 0;
5629
+ } catch {
5630
+ return void 0;
5631
+ }
5632
+ }
5474
5633
  async function wsRetire(ws, opts) {
5475
5634
  const cloud = cloudBase(opts.cloud);
5476
- const { signAndPostWorkspaceRetirement: signAndPostWorkspaceRetirement2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
5635
+ const { signAndPostChildLifecycle: signAndPostChildLifecycle2, signAndPostWorkspaceRetirement: signAndPostWorkspaceRetirement2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
5477
5636
  const parent = opts.parent ?? "root";
5478
5637
  let record;
5479
5638
  try {
@@ -5485,12 +5644,29 @@ async function wsRetire(ws, opts) {
5485
5644
  }
5486
5645
  const recordId = record?.id;
5487
5646
  if (recordId === void 0) {
5488
- err4(
5489
- `retire '${ws}': no CloudWorkspace record named '${ws}' in ${parent}'s registry \u2014 nothing lawful to retire.
5490
- (a workspace born via the OPEN lane \u2014 bare POST /v2/workspaces/${ws} \u2014 has no registry row;
5491
- registered births ride \`githolon ws create ${ws} --via ${parent}\`. An unregistered leak needs a custodian sweep.)`
5492
- );
5493
- return 1;
5647
+ const childRecord = await childBirthRecord(cloud, parent, ws);
5648
+ if (childRecord === void 0) {
5649
+ err4(
5650
+ `retire '${ws}': no CloudWorkspace or NomosBirthRecord named '${ws}' in ${parent}'s registry \u2014 nothing lawful to retire.
5651
+ An out-of-band custody repo has no parent lifecycle fact and needs a custodian investigation.`
5652
+ );
5653
+ return 1;
5654
+ }
5655
+ const creator = childRecord.data?.principal ?? "";
5656
+ let childVerdict;
5657
+ try {
5658
+ childVerdict = await signAndPostChildLifecycle2(parent, ws, creator, "retire", opts);
5659
+ } catch (e) {
5660
+ err4(e.message);
5661
+ return 1;
5662
+ }
5663
+ if (!childVerdict.ok) {
5664
+ err4(`retire '${ws}' refused (${childVerdict.status}): ${typeof childVerdict.body === "string" ? childVerdict.body : JSON.stringify(childVerdict.body)}`);
5665
+ return 1;
5666
+ }
5667
+ out4(`\u2713 child ${ws} retired via signed lifecycle offer to ${parent} on ${cloud}`);
5668
+ out4(" custody is retained; reclaim is a separate, law-gated terminal step");
5669
+ return 0;
5494
5670
  }
5495
5671
  let v;
5496
5672
  try {
@@ -5519,17 +5695,30 @@ async function wsRetire(ws, opts) {
5519
5695
  }
5520
5696
  async function wsReclaim(ws, opts) {
5521
5697
  const cloud = cloudBase(opts.cloud);
5522
- const { signAndPostGovernance: signAndPostGovernance2, resolveGovernancePrincipal: resolveGovernancePrincipal2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
5698
+ const { signAndPostChildLifecycle: signAndPostChildLifecycle2, signAndPostGovernance: signAndPostGovernance2, resolveGovernancePrincipal: resolveGovernancePrincipal2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
5523
5699
  const parent = opts.parent ?? "root";
5524
5700
  let v;
5525
5701
  try {
5526
5702
  const principal = resolveGovernancePrincipal2(opts);
5527
- v = await signAndPostGovernance2(
5528
- parent,
5529
- "reclaimWorkspace",
5530
- { workspaceId: ws, reclaimedBy: principal, reclaimedAt: Date.now() },
5531
- opts
5532
- );
5703
+ const q = await fetch(`${cloud}/v2/workspaces/${parent}/workspaceByName?name=${encodeURIComponent(ws)}`);
5704
+ const qd = await q.json().catch(() => void 0);
5705
+ const rows = qd?.ok === true ? qd.rows ?? [] : [];
5706
+ const workspace = rows.find((r) => r.data?.status === "retired") ?? rows[0];
5707
+ if (workspace?.id !== void 0) {
5708
+ v = await signAndPostGovernance2(
5709
+ parent,
5710
+ "reclaimWorkspace",
5711
+ { workspaceId: workspace.id, reclaimedBy: principal, reclaimedAt: Date.now() },
5712
+ opts
5713
+ );
5714
+ } else {
5715
+ const child = await childBirthRecord(cloud, parent, ws);
5716
+ if (child === void 0) {
5717
+ err4(`reclaim '${ws}': no CloudWorkspace or NomosBirthRecord named '${ws}' in ${parent}'s registry`);
5718
+ return 1;
5719
+ }
5720
+ v = await signAndPostChildLifecycle2(parent, ws, child.data?.principal ?? "", "reclaim", opts);
5721
+ }
5533
5722
  } catch (e) {
5534
5723
  err4(e.message);
5535
5724
  return 1;
@@ -5538,7 +5727,7 @@ async function wsReclaim(ws, opts) {
5538
5727
  err4(`reclaim '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
5539
5728
  return 1;
5540
5729
  }
5541
- out4(`\u2713 workspace ${ws} reclaimed via signed governance offer to ${parent} on ${cloud}`);
5730
+ out4(`\u2713 workspace ${ws} reclaimed via signed lifecycle offer to ${parent} on ${cloud}`);
5542
5731
  out4(" the custodian drops the ledger bytes on seeing this fact folded; the reclaimed FACT is append-only");
5543
5732
  return 0;
5544
5733
  }
@@ -5570,6 +5759,22 @@ async function deploy2(ws, opts) {
5570
5759
  return 1;
5571
5760
  }
5572
5761
  const fileName = file.split("/").pop();
5762
+ try {
5763
+ const { ensureApplicationWorkspace: ensureApplicationWorkspace2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
5764
+ const prepared = await ensureApplicationWorkspace2(ws, opts);
5765
+ if (prepared.deployPrincipal !== void 0 && opts.principal === void 0) {
5766
+ opts = {
5767
+ ...opts,
5768
+ principal: prepared.deployPrincipal.startsWith("user:") ? prepared.deployPrincipal.slice("user:".length) : prepared.deployPrincipal
5769
+ };
5770
+ }
5771
+ if (prepared.created) {
5772
+ out4(` created application ${ws} under your Nomos home`);
5773
+ }
5774
+ } catch (e) {
5775
+ err4(`deploy '${ws}' could not prepare the application: ${e.message}`);
5776
+ return 1;
5777
+ }
5573
5778
  const beforeLaw = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((br) => br.json()).then((bd) => bd.ok === true ? bd : {}).catch(() => ({}));
5574
5779
  const before = beforeLaw.domains ?? [];
5575
5780
  const currentLaw2 = beforeLaw.currentLaw ?? {};
@@ -1846,32 +1846,42 @@ function aliasIdsFor(x, path2) {
1846
1846
  return new Set(ids);
1847
1847
  }
1848
1848
  function resolveGatewayOperation(spec, opName, method) {
1849
- if (!spec || !spec.paths || !opName || !method) return { op: null };
1849
+ if (!spec || !opName || !method) return { op: null };
1850
+ const routeTables = [
1851
+ spec.paths ?? {},
1852
+ spec["x-nomos-framework-routes"]?.paths ?? {}
1853
+ ];
1850
1854
  const normalizedMethod = String(method).toLowerCase();
1851
1855
  const exactPath = `/${String(opName).replace(/^\/+/, "")}`;
1852
- const exact = spec.paths[exactPath]?.[normalizedMethod];
1853
- if (exact) return { op: exact, path: exactPath, ambiguous: null };
1856
+ for (const paths of routeTables) {
1857
+ const exact = paths[exactPath]?.[normalizedMethod];
1858
+ if (exact) return { op: exact, path: exactPath, ambiguous: null };
1859
+ }
1854
1860
  const slash = String(opName).indexOf("/");
1855
1861
  if (slash > 0) {
1856
1862
  const requestedDomain = String(opName).slice(0, slash);
1857
1863
  const requestedAlias = String(opName).slice(slash + 1);
1858
1864
  const matches2 = [];
1859
- for (const [path2, methods] of Object.entries(spec.paths)) {
1860
- const op = methods?.[normalizedMethod];
1861
- const x = op?.["x-nomos"];
1862
- if (!op || !x || x.domain !== requestedDomain) continue;
1863
- if (aliasIdsFor(x, path2).has(requestedAlias)) matches2.push({ op, path: path2 });
1865
+ for (const paths of routeTables) {
1866
+ for (const [path2, methods] of Object.entries(paths)) {
1867
+ const op = methods?.[normalizedMethod];
1868
+ const x = op?.["x-nomos"];
1869
+ if (!op || !x || x.domain !== requestedDomain) continue;
1870
+ if (aliasIdsFor(x, path2).has(requestedAlias)) matches2.push({ op, path: path2 });
1871
+ }
1864
1872
  }
1865
1873
  if (matches2.length === 1) return { ...matches2[0], ambiguous: null };
1866
1874
  if (matches2.length > 1) return { op: null, ambiguous: matches2.map((m) => m.path).sort() };
1867
1875
  return { op: null, ambiguous: null };
1868
1876
  }
1869
1877
  const matches = [];
1870
- for (const [path2, methods] of Object.entries(spec.paths)) {
1871
- const op = methods?.[normalizedMethod];
1872
- const x = op?.["x-nomos"];
1873
- if (!op || !x) continue;
1874
- if (aliasIdsFor(x, path2).has(opName)) matches.push({ op, path: path2 });
1878
+ for (const paths of routeTables) {
1879
+ for (const [path2, methods] of Object.entries(paths)) {
1880
+ const op = methods?.[normalizedMethod];
1881
+ const x = op?.["x-nomos"];
1882
+ if (!op || !x) continue;
1883
+ if (aliasIdsFor(x, path2).has(opName)) matches.push({ op, path: path2 });
1884
+ }
1875
1885
  }
1876
1886
  if (matches.length === 1) return { ...matches[0], ambiguous: null };
1877
1887
  if (matches.length > 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githolon",
3
- "version": "0.100.2",
3
+ "version": "0.100.4",
4
4
  "type": "module",
5
5
  "description": "githolon — the Nomos developer CLI: Rails-style generators for @githolon/dsl domains + the package compiler. Kernel-independent.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@bjorn3/browser_wasi_shim": "0.4.2",
32
- "@githolon/client": "^0.100.2",
33
- "@githolon/dsl": "^0.100.2",
32
+ "@githolon/client": "^0.100.4",
33
+ "@githolon/dsl": "^0.100.4",
34
34
  "isomorphic-git": "^1.38.4"
35
35
  },
36
36
  "devDependencies": {