githolon 0.100.6 → 0.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -329,6 +329,131 @@ var init_custody_transport = __esm({
329
329
  }
330
330
  });
331
331
 
332
+ // vendor/engine/managed-promotion.ts
333
+ function prepareManagedPromotion(report, candidateHash, domain, opts = {}) {
334
+ if (!report.sourceVerified || !report.complete || !report.evolve?.ok) {
335
+ const reason = !report.sourceVerified ? "the source ledger did not verify" : !report.complete ? "some historical premises could not be re-derived" : "the candidate fails deterministic schema evolution";
336
+ return { ok: false, stage: "preflight", report, error: `promotion refused: ${reason}` };
337
+ }
338
+ const acceptanceReason = opts.acceptanceReason?.trim();
339
+ if (!report.promotable && !acceptanceReason) {
340
+ return {
341
+ ok: false,
342
+ stage: "acceptance",
343
+ report,
344
+ error: "exact history changes under the candidate; provide an acceptanceReason to make that business decision accountable"
345
+ };
346
+ }
347
+ if (!report.atStateRoot || !report.historyHash || !report.reportHash || !report.projection) {
348
+ return {
349
+ ok: false,
350
+ stage: "preflight",
351
+ report,
352
+ error: "the complete pre-flight report is missing its state, projection, or content proof"
353
+ };
354
+ }
355
+ const provedDomains = report.domains?.length ? report.domains : [report.domain];
356
+ if (!provedDomains.includes(domain)) {
357
+ return {
358
+ ok: false,
359
+ stage: "preflight",
360
+ report,
361
+ error: `the package-wide report does not cover the generated business area '${domain}'`
362
+ };
363
+ }
364
+ const reportEvidence = JSON.parse(JSON.stringify(report));
365
+ if (Array.isArray(reportEvidence.divergences)) {
366
+ for (const divergence of reportEvidence.divergences) {
367
+ if (divergence && typeof divergence === "object") {
368
+ delete divergence.atCommit;
369
+ }
370
+ }
371
+ }
372
+ const preflight = {
373
+ candidateHash,
374
+ // A package may be reached through any one of its generated domain clients. The
375
+ // kernel chooses one canonical domain for the private receipt so the exact same
376
+ // package/history proof has one identity regardless of that entry point.
377
+ domain: report.domain,
378
+ atHash: report.atHash,
379
+ atStateRoot: report.atStateRoot,
380
+ historyHash: report.historyHash,
381
+ reportHash: report.reportHash,
382
+ reportJson: JSON.stringify(reportEvidence),
383
+ complete: true,
384
+ evolveOk: true,
385
+ rawPromotable: report.promotable,
386
+ projectionEqual: report.projection.equal,
387
+ replayedIntents: report.replayedIntents,
388
+ candidateIntents: report.candidateIntents,
389
+ divergenceCount: report.divergences.length,
390
+ baselineRoot: report.projection.baselineRoot,
391
+ candidateRoot: report.projection.candidateRoot,
392
+ ...!report.promotable && acceptanceReason ? { acceptanceReason } : {}
393
+ };
394
+ return {
395
+ ok: true,
396
+ stage: "ready",
397
+ report,
398
+ receiptId: `${RELEASE_RECEIPT_PREFIX}${report.reportHash}`,
399
+ payload: {
400
+ domainHash: candidateHash,
401
+ ...opts.replaces ? { replaces: opts.replaces } : {},
402
+ retireReplaced: opts.retireReplaced ?? false,
403
+ ...opts.dispositions ? { dispositions: opts.dispositions } : {},
404
+ preflight
405
+ }
406
+ };
407
+ }
408
+ function prepareManagedRestoration(report, currentHash, priorHash, domain, opts = {}) {
409
+ if (!report.dispositions || typeof report.dispositions !== "object") {
410
+ return {
411
+ ok: false,
412
+ stage: "preflight",
413
+ report,
414
+ error: "restoration refused: the exact-history proof did not derive its predecessor dispositions"
415
+ };
416
+ }
417
+ const acceptanceReason = opts.acceptanceReason?.trim();
418
+ const prepared = prepareManagedPromotion(report, priorHash, domain, {
419
+ dispositions: report.dispositions,
420
+ ...acceptanceReason ? { acceptanceReason } : {}
421
+ });
422
+ if (!prepared.ok) {
423
+ return {
424
+ ...prepared,
425
+ stage: prepared.stage === "acceptance" ? "acceptance" : "preflight"
426
+ };
427
+ }
428
+ if (!acceptanceReason) {
429
+ return {
430
+ ok: false,
431
+ stage: "acceptance",
432
+ report,
433
+ error: "restoring previous law needs a business reason even when historical outcomes are unchanged"
434
+ };
435
+ }
436
+ return {
437
+ ok: true,
438
+ stage: "ready",
439
+ report,
440
+ receiptId: prepared.receiptId,
441
+ payload: {
442
+ candidateHash: currentHash,
443
+ priorHash,
444
+ dispositions: report.dispositions,
445
+ preflight: prepared.payload.preflight
446
+ }
447
+ };
448
+ }
449
+ var RELEASE_RECEIPT_PREFIX;
450
+ var init_managed_promotion = __esm({
451
+ "vendor/engine/managed-promotion.ts"() {
452
+ "use strict";
453
+ RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
454
+ }
455
+ });
456
+
332
457
  // vendor/engine/engine.ts
333
458
  var engine_exports = {};
334
459
  __export(engine_exports, {
@@ -360,6 +485,10 @@ __export(engine_exports, {
360
485
  countTenantRowsByHome: () => countTenantRowsByHome,
361
486
  createEngine: () => createEngine,
362
487
  cryptoKeygen: () => cryptoKeygen,
488
+ cryptoOwnedDeviceRequest: () => cryptoOwnedDeviceRequest,
489
+ cryptoOwnedPrepareApproval: () => cryptoOwnedPrepareApproval,
490
+ cryptoOwnedPrepareRecovery: () => cryptoOwnedPrepareRecovery,
491
+ cryptoOwnedScopeKey: () => cryptoOwnedScopeKey,
363
492
  cryptoSealField: () => cryptoSealField,
364
493
  cryptoUnwrapKey: () => cryptoUnwrapKey,
365
494
  cryptoWrapKey: () => cryptoWrapKey,
@@ -383,6 +512,8 @@ __export(engine_exports, {
383
512
  flushDeferredProjections: () => flushDeferredProjections,
384
513
  gitdirOf: () => gitdirOf,
385
514
  globalAggregates: () => globalAggregates,
515
+ historyPreflight: () => historyPreflight,
516
+ historyRestorePreflight: () => historyRestorePreflight,
386
517
  importCheckpoint: () => importCheckpoint,
387
518
  ingestPackAsCustody: () => ingestPackAsCustody,
388
519
  installPayload: () => installPayload,
@@ -405,9 +536,11 @@ __export(engine_exports, {
405
536
  packBornChildren: () => packBornChildren,
406
537
  packDelta: () => packDelta,
407
538
  parseAttestedReadNeed: () => parseAttestedReadNeed,
539
+ pendingConflictSummary: () => pendingConflictSummary,
408
540
  pendingConflicts: () => pendingConflicts,
409
541
  placementDirectives: () => placementDirectives,
410
542
  projectionStats: () => projectionStats,
543
+ promotePreflighted: () => promotePreflighted,
411
544
  pushCheckpoint: () => pushCheckpoint,
412
545
  pushShape: () => pushShape,
413
546
  pushStagedCheckpoint: () => pushStagedCheckpoint,
@@ -425,6 +558,7 @@ __export(engine_exports, {
425
558
  replicateIntents: () => replicateIntents,
426
559
  repoArgOf: () => repoArgOf,
427
560
  restoreCheckpointFromCustody: () => restoreCheckpointFromCustody,
561
+ restorePreviousPreflighted: () => restorePreviousPreflighted,
428
562
  scopedReads: () => scopedReads,
429
563
  sealAttestationBatches: () => sealAttestationBatches,
430
564
  sealHomes: () => sealHomes,
@@ -435,6 +569,7 @@ __export(engine_exports, {
435
569
  spatialBboxParams: () => spatialBboxParams,
436
570
  spatialWithin: () => spatialWithin,
437
571
  stageCheckpoint: () => stageCheckpoint,
572
+ stagePayload: () => stagePayload,
438
573
  stats: () => stats,
439
574
  sum: () => sum,
440
575
  summaryCloseAgainstHeld: () => summaryCloseAgainstHeld,
@@ -1415,6 +1550,9 @@ function mintId(eng, typeTag) {
1415
1550
  function author(eng, ws, domain, directiveId, payload, controllerHash, opts = {}) {
1416
1551
  const seq = eng.seq++;
1417
1552
  const capPorts = { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(opts.rngDraws ?? 64) };
1553
+ if (opts.__nomosConflictSnapshots !== void 0 && opts.__nomosConflictSnapshots.length > 0) {
1554
+ capPorts.conflict_snapshots = opts.__nomosConflictSnapshots;
1555
+ }
1418
1556
  if (opts.validFromEpoch !== void 0) capPorts.valid_from_epoch = opts.validFromEpoch;
1419
1557
  if (opts.principalVerified !== void 0) {
1420
1558
  capPorts.principal = opts.actor ?? "";
@@ -2214,6 +2352,120 @@ async function verifyChain(eng, ws, ledger) {
2214
2352
  function verifyChainLocal(eng, ws) {
2215
2353
  return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "verifyChain" }), branch: BRANCH }, eng.STDERR));
2216
2354
  }
2355
+ function historyPreflight(eng, ws, domain, candidateHash, atHash, dispositions) {
2356
+ return JSON.parse(call(eng.ex, "query", {
2357
+ repoArg: repoArgOf(ws),
2358
+ workspace: ws,
2359
+ branch: BRANCH,
2360
+ queryBytes: b64Json({
2361
+ op: "historyPreflight",
2362
+ domain,
2363
+ candidateHash,
2364
+ atHash,
2365
+ ...dispositions ? { dispositions } : {}
2366
+ })
2367
+ }, eng.STDERR));
2368
+ }
2369
+ function historyRestorePreflight(eng, ws, domain, currentHash, priorHash, atHash, actor) {
2370
+ return JSON.parse(call(eng.ex, "query", {
2371
+ repoArg: repoArgOf(ws),
2372
+ workspace: ws,
2373
+ branch: BRANCH,
2374
+ queryBytes: b64Json({
2375
+ op: "historyPreflight",
2376
+ domain,
2377
+ candidateHash: priorHash,
2378
+ atHash,
2379
+ restorePreviousOf: currentHash,
2380
+ ackBy: actor
2381
+ })
2382
+ }, eng.STDERR));
2383
+ }
2384
+ function promotePreflighted(eng, ws, domain, candidateHash, opts = {}) {
2385
+ const actingPrincipal = opts.actor?.trim() || String(eng.installedBy || "").trim();
2386
+ if (!actingPrincipal && !opts.authToken) {
2387
+ return {
2388
+ ok: false,
2389
+ stage: "identity",
2390
+ error: "promotion needs the stateful client's current principal so its business decision is attributable"
2391
+ };
2392
+ }
2393
+ const atHash = kgit.resolveRef(eng, gitdirOf(ws), BRANCH);
2394
+ if (!atHash) {
2395
+ return { ok: false, stage: "preflight", error: "workspace has no history to prove" };
2396
+ }
2397
+ let report;
2398
+ try {
2399
+ report = historyPreflight(eng, ws, domain, candidateHash, atHash, opts.dispositions);
2400
+ } catch (error) {
2401
+ return { ok: false, stage: "preflight", error: String(error?.message || error) };
2402
+ }
2403
+ const prepared = prepareManagedPromotion(report, candidateHash, domain, opts);
2404
+ if (!prepared.ok) return prepared;
2405
+ const {
2406
+ replaces: _replaces,
2407
+ retireReplaced: _retireReplaced,
2408
+ dispositions: _dispositions,
2409
+ acceptanceReason: _acceptance,
2410
+ ...authorOpts
2411
+ } = opts;
2412
+ const result = author(eng, ws, "nomos", "promoteDomain", prepared.payload, eng.hashes.nomos, {
2413
+ ...authorOpts,
2414
+ ...actingPrincipal ? { actor: actingPrincipal } : {}
2415
+ });
2416
+ return result.ok ? { ...result, stage: "promoted", report, receiptId: prepared.receiptId } : { ...result, stage: "admission", report, receiptId: prepared.receiptId };
2417
+ }
2418
+ function restorePreviousPreflighted(eng, ws, domain, currentHash, opts = {}) {
2419
+ const actingPrincipal = opts.actor?.trim() || String(eng.installedBy || "").trim();
2420
+ if (!actingPrincipal) {
2421
+ return {
2422
+ ok: false,
2423
+ stage: "identity",
2424
+ error: "model restoration needs the stateful client's current principal so its business decision is attributable"
2425
+ };
2426
+ }
2427
+ const resolved = currentLaw(eng, ws)[domain];
2428
+ if (resolved !== currentHash) {
2429
+ return {
2430
+ ok: false,
2431
+ stage: "preflight",
2432
+ error: "the generated model is no longer current; nothing changed [nomos.rollout.restore-current-changed]"
2433
+ };
2434
+ }
2435
+ const current = qById(eng, ws, `domain-installation:${currentHash}`)[0]?.data;
2436
+ const priorHash = typeof current?.["spec.supersedes"] === "string" ? current["spec.supersedes"] : "";
2437
+ if (!priorHash) {
2438
+ return {
2439
+ ok: false,
2440
+ stage: "preflight",
2441
+ error: "this model has no previous model to restore [nomos.rollout.restore-no-previous]"
2442
+ };
2443
+ }
2444
+ const atHash = kgit.resolveRef(eng, gitdirOf(ws), BRANCH);
2445
+ if (!atHash) return { ok: false, stage: "preflight", error: "workspace has no history to prove" };
2446
+ let report;
2447
+ try {
2448
+ report = historyRestorePreflight(
2449
+ eng,
2450
+ ws,
2451
+ domain,
2452
+ currentHash,
2453
+ priorHash,
2454
+ atHash,
2455
+ actingPrincipal
2456
+ );
2457
+ } catch (error) {
2458
+ return { ok: false, stage: "preflight", error: String(error?.message || error) };
2459
+ }
2460
+ const prepared = prepareManagedRestoration(report, currentHash, priorHash, domain, opts);
2461
+ if (!prepared.ok) return prepared;
2462
+ const { acceptanceReason: _acceptance, ...authorOpts } = opts;
2463
+ const result = author(eng, ws, "nomos", "rollbackDomain", prepared.payload, eng.hashes.nomos, {
2464
+ ...authorOpts,
2465
+ ...actingPrincipal ? { actor: actingPrincipal } : {}
2466
+ });
2467
+ return result.ok ? { ...result, stage: "restored", report, receiptId: prepared.receiptId } : { ...result, stage: "admission", report, receiptId: prepared.receiptId };
2468
+ }
2217
2469
  async function rebirthCompact(eng, ws, ledger, { reason = "", supersededAt = 0, parent = "" } = {}) {
2218
2470
  const incWs = `${ws}__rebirth`;
2219
2471
  try {
@@ -2526,26 +2778,89 @@ async function boot(eng, ws, ledger) {
2526
2778
  );
2527
2779
  }
2528
2780
  async function deploy(eng, ws, ledger, usda, domainHash, dispositions, opts = {}) {
2529
- const result = author(eng, ws, "nomos", "installDomain", installPayload(domainHash, usda, eng.installedBy, dispositions), eng.hashes.nomos, { actor: opts.actor ?? "", ...opts.authToken ? { authToken: opts.authToken } : {} });
2530
- if (result.ok === false) {
2781
+ const existing = qById(eng, ws, `domain-installation:${domainHash}`);
2782
+ if (existing.length > 0) {
2783
+ const phase = String(existing[0]?.data?.["status.phase"] ?? "");
2784
+ return {
2785
+ head: kgit.resolveRef(eng, gitdirOf(ws), BRANCH),
2786
+ installation: existing,
2787
+ staged: phase === "Pending",
2788
+ alreadyInstalled: true
2789
+ };
2790
+ }
2791
+ const before = currentLaw(eng, ws);
2792
+ const tenantHashes = [...new Set(Object.values(before).filter(
2793
+ (hash) => typeof hash === "string" && hash !== eng.hashes.bootstrap && hash !== eng.hashes.nomos
2794
+ ))];
2795
+ const upgrade = tenantHashes.length > 0;
2796
+ let supersedes = opts.supersedes?.trim() || void 0;
2797
+ if (supersedes !== void 0 && !tenantHashes.includes(supersedes)) {
2531
2798
  throw new Error(
2532
- `deploy-refused: installing ${domainHash.slice(0, 12)}\u2026 was REFUSED by the one gate (custody unchanged): ${result.error ?? "unknown refusal"}`
2799
+ `deploy-predecessor-changed: the client named ${supersedes.slice(0, 12)}\u2026 as the current model, but this workspace no longer serves it. Refresh and deploy again; nothing was committed.`
2533
2800
  );
2534
2801
  }
2535
- const installation = qById(eng, ws, `domain-installation:${domainHash}`);
2536
- const keys = installation[0]?.data?.domainKeys ?? installation[0]?.domainKeys ?? [];
2537
- if (keys.length > 0) {
2538
- const law = installedLaw(eng, ws);
2539
- const currentLaw2 = law?.currentLaw ?? {};
2540
- const stale = keys.filter((k) => currentLaw2[k] !== void 0 && currentLaw2[k] !== domainHash);
2541
- if (stale.length > 0) {
2802
+ if (upgrade && supersedes === void 0) {
2803
+ if (tenantHashes.length === 1) supersedes = tenantHashes[0];
2804
+ else {
2542
2805
  throw new Error(
2543
- `deploy-did-not-advance: the install of ${domainHash.slice(0, 12)}\u2026 ADMITTED but the workspace's current law still serves ${stale.map((k) => `'${k}' from ${String(currentLaw2[k]).slice(0, 12)}\u2026`).join(", ")} \u2014 refusing (custody unchanged) instead of a silent no-op. This usually means the existing installation's era/lineage prevents supersession: run \`githolon deploy --dry-run=evolve\` for the gate's verdict, supply #58 dispositions for a true removal/retype, or re-forge the workspace onto the current lineage.`
2806
+ `deploy-predecessor-ambiguous: this workspace currently serves ${tenantHashes.length} business-model packages. Nomos will not guess which one this candidate follows; use the generated deploy client, which binds the candidate to the current model before staging. Nothing was committed.`
2544
2807
  );
2545
2808
  }
2546
2809
  }
2547
- await commitAndPush(eng, ws, ledger);
2548
- return { head: result.head, installation };
2810
+ if (upgrade && dispositions && Object.keys(dispositions).length > 0) {
2811
+ throw new Error(
2812
+ "deploy-manual-evolution-refused: successor deployment no longer accepts caller-chosen schema dispositions. Schema evolution belongs to the candidate model's adoption proof; nothing was committed."
2813
+ );
2814
+ }
2815
+ const directiveId = upgrade ? "stageDomain" : "installDomain";
2816
+ const payload = upgrade ? stagePayload(domainHash, usda, eng.installedBy, supersedes) : installPayload(domainHash, usda, eng.installedBy, dispositions);
2817
+ const result = author(eng, ws, "nomos", directiveId, payload, eng.hashes.nomos, {
2818
+ actor: opts.actor ?? "",
2819
+ ...opts.authToken ? { authToken: opts.authToken } : {}
2820
+ });
2821
+ if (result.ok === false) {
2822
+ unmount(eng, ws);
2823
+ throw new Error(
2824
+ `deploy-refused: ${upgrade ? "staging" : "installing"} ${domainHash.slice(0, 12)}\u2026 was REFUSED by the one gate (custody unchanged): ${result.error ?? "unknown refusal"}`
2825
+ );
2826
+ }
2827
+ try {
2828
+ const installation = qById(eng, ws, `domain-installation:${domainHash}`);
2829
+ if (upgrade) {
2830
+ const phase = String(installation[0]?.data?.["status.phase"] ?? "");
2831
+ if (phase !== "Pending") {
2832
+ throw new Error(
2833
+ `deploy-did-not-stage: candidate ${domainHash.slice(0, 12)}\u2026 admitted but its lifecycle phase is '${phase || "missing"}', not Pending. Custody was not pushed.`
2834
+ );
2835
+ }
2836
+ const after = currentLaw(eng, ws);
2837
+ const moved = Object.entries(before).filter(([key, hash]) => after[key] !== hash);
2838
+ if (moved.length > 0 || Object.values(after).includes(domainHash)) {
2839
+ throw new Error(
2840
+ `deploy-activated-candidate: staging ${domainHash.slice(0, 12)}\u2026 changed served law (${moved.map(([key]) => key).join(", ") || "candidate became current"}). Nomos refuses to persist an upgrade that bypassed model.adopt().`
2841
+ );
2842
+ }
2843
+ await commitAndPush(eng, ws, ledger);
2844
+ return { head: result.head, installation, staged: true, supersedes };
2845
+ }
2846
+ const law = installedLaw(eng, ws);
2847
+ const installed = (law?.domains ?? []).find((row) => row?.domainHash === domainHash);
2848
+ const keys = installed?.domainKeys ?? installation[0]?.data?.domainKeys ?? installation[0]?.domainKeys ?? [];
2849
+ if (keys.length > 0) {
2850
+ const currentLaw2 = law?.currentLaw ?? {};
2851
+ const stale = keys.filter((k) => currentLaw2[k] !== domainHash);
2852
+ if (stale.length > 0) {
2853
+ throw new Error(
2854
+ `deploy-did-not-advance: the install of ${domainHash.slice(0, 12)}\u2026 ADMITTED but the workspace's current law still serves ${stale.map((k) => `'${k}' from ${String(currentLaw2[k]).slice(0, 12)}\u2026`).join(", ")} \u2014 refusing (custody unchanged) instead of a silent no-op. This usually means the existing installation's era/lineage prevents supersession: run \`githolon deploy --dry-run=evolve\` for the gate's verdict, supply #58 dispositions for a true removal/retype, or re-forge the workspace onto the current lineage.`
2855
+ );
2856
+ }
2857
+ }
2858
+ await commitAndPush(eng, ws, ledger);
2859
+ return { head: result.head, installation, staged: false };
2860
+ } catch (error) {
2861
+ unmount(eng, ws);
2862
+ throw error;
2863
+ }
2549
2864
  }
2550
2865
  function snapshot(eng, ws) {
2551
2866
  const wsDir = dirOf(eng.preopen.dir.contents.get("ws")).contents.get(ws);
@@ -2645,13 +2960,14 @@ function sealAttestationBatches(eng, ws, domain, controllerHash, opts = {}) {
2645
2960
  }
2646
2961
  return outcomes;
2647
2962
  }
2648
- var enc3, dec3, BRANCH, RESULT_PACK_INLINE_MAX, dirOf, repoArgOf, gitdirOf, unpack, log, repoArgOfGitdir, bytesFromB64, kgit, installPayload, routeTagHexOfHomeKey, SUMMARY_SEP, summaryKey, splitSummaryKey, NOMOS_SHARD_ROWS_READ, groupOf, tallyOf, isExtremumRead, extremumMonotone, CKPT_REF, pushVia, fullRef, applyPackInto, custodyLsRefs, qById, query, check, expand, cryptoSealField, cryptoKeygen, authorKeygen, eraCertSign, delegationCertSign, attestationSign, attestedRead, certSign, cryptoWrapKey, cryptoUnwrapKey, readPosture, count, sum, pendingConflicts, spatialBboxParams, spatialWithin, extremum, nomosActive, refusedRefName, outboxRefName, GENESIS_WARRANT_ID, isWarranted, projectionStats;
2963
+ var enc3, dec3, BRANCH, RESULT_PACK_INLINE_MAX, dirOf, repoArgOf, gitdirOf, unpack, log, repoArgOfGitdir, bytesFromB64, kgit, installPayload, stagePayload, routeTagHexOfHomeKey, SUMMARY_SEP, summaryKey, splitSummaryKey, NOMOS_SHARD_ROWS_READ, groupOf, tallyOf, isExtremumRead, extremumMonotone, CKPT_REF, pushVia, fullRef, applyPackInto, custodyLsRefs, qById, query, check, expand, cryptoSealField, cryptoOwnedScopeKey, cryptoOwnedDeviceRequest, cryptoOwnedPrepareApproval, cryptoOwnedPrepareRecovery, cryptoKeygen, authorKeygen, eraCertSign, delegationCertSign, attestationSign, attestedRead, certSign, cryptoWrapKey, cryptoUnwrapKey, readPosture, count, sum, pendingConflicts, pendingConflictSummary, spatialBboxParams, spatialWithin, extremum, nomosActive, refusedRefName, outboxRefName, GENESIS_WARRANT_ID, isWarranted, projectionStats;
2649
2964
  var init_engine = __esm({
2650
2965
  "vendor/engine/engine.ts"() {
2651
2966
  "use strict";
2652
2967
  init_git_fs();
2653
2968
  init_tree();
2654
2969
  init_custody_transport();
2970
+ init_managed_promotion();
2655
2971
  enc3 = new TextEncoder();
2656
2972
  dec3 = new TextDecoder();
2657
2973
  BRANCH = "main";
@@ -2697,6 +3013,14 @@ var init_engine = __esm({
2697
3013
  }, eng.STDERR)).oid
2698
3014
  };
2699
3015
  installPayload = (hash, usda, installedBy, dispositions) => ({ domainHash: hash, packageUsda: usda, installedBy, dependencies: [], finalizers: [], ...dispositions ? { dispositions } : {} });
3016
+ stagePayload = (hash, usda, installedBy, supersedes) => ({
3017
+ domainHash: hash,
3018
+ packageUsda: usda,
3019
+ installedBy,
3020
+ dependencies: [],
3021
+ ...supersedes ? { supersedes } : {},
3022
+ finalizers: []
3023
+ });
2700
3024
  routeTagHexOfHomeKey = async (homeKey) => (await sha256hex(`nomos-route:${homeKey}`)).slice(0, 12);
2701
3025
  SUMMARY_SEP = "";
2702
3026
  summaryKey = (readId, group) => `${readId}${SUMMARY_SEP}${group}`;
@@ -2727,6 +3051,105 @@ var init_engine = __esm({
2727
3051
  check = (eng, ws, object, relation, subject) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "check", object, relation, subject }), branch: BRANCH }, eng.STDERR));
2728
3052
  expand = (eng, ws, object, relation) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "expand", object, relation }), branch: BRANCH }, eng.STDERR));
2729
3053
  cryptoSealField = (eng, { scopeKey, scope, epoch, aggSid, fieldSid, plaintext }) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "cryptoSealField", scopeKey, scope, epoch, aggSid, fieldSid, plaintext }) }, eng.STDERR)).envelope;
3054
+ cryptoOwnedScopeKey = (eng, workspace, authorSecret, scope, principal = "", requireSeat = true, record, recordEpoch = 0, recordWrap) => {
3055
+ const recordObject = record === void 0 ? void 0 : `${record.type}:${record.id}`;
3056
+ const derived = JSON.parse(call(eng.ex, "query", {
3057
+ queryBytes: b64Json({
3058
+ op: "cryptoOwnedScopeKey",
3059
+ workspace,
3060
+ principal,
3061
+ authorSecret,
3062
+ scope,
3063
+ ...recordObject === void 0 ? {} : { recordObject, recordEpoch },
3064
+ ...recordWrap === void 0 ? {} : { recordWrap }
3065
+ })
3066
+ }, eng.STDERR));
3067
+ const ownedKeySubject = String(derived.ownedKeySubject || "");
3068
+ if (!requireSeat) {
3069
+ return {
3070
+ scope: String(derived.scope || scope),
3071
+ epoch: Number(derived.epoch) || 0,
3072
+ key: String(derived.scopeKey || ""),
3073
+ ownedKeySubject,
3074
+ ownedCustody: typeof derived.ownedCustody === "string" ? derived.ownedCustody : void 0,
3075
+ keyCommitment: typeof derived.keyCommitment === "string" ? derived.keyCommitment : void 0,
3076
+ principalWrappingPublic: typeof derived.principalWrappingPublic === "string" ? derived.principalWrappingPublic : void 0
3077
+ };
3078
+ }
3079
+ if (principal) {
3080
+ let hex = "";
3081
+ for (const byte of enc3.encode(principal)) hex += byte.toString(16).padStart(2, "0");
3082
+ const object = `nomos-owned-principal:${hex || "00"}`;
3083
+ const rows = qById(eng, workspace, `reltuple-${object}-nomos_internal_owned_device_key_7c1f5e`);
3084
+ const row = Array.isArray(rows) ? rows[0] : rows?.rows?.[0];
3085
+ const leaves = row?.data?.subjects ?? row?.subjects ?? {};
3086
+ const leaf = (raw) => {
3087
+ if (raw && typeof raw === "object") return raw;
3088
+ try {
3089
+ return typeof raw === "string" ? JSON.parse(raw) : {};
3090
+ } catch {
3091
+ return {};
3092
+ }
3093
+ };
3094
+ const active = Object.entries(leaves).filter(([, raw]) => leaf(raw).status === "active").map(([subject]) => subject);
3095
+ if (active.length === 0 && Object.keys(leaves).length === 0) {
3096
+ return {
3097
+ scope: String(derived.scope || scope),
3098
+ epoch: Number(derived.epoch) || 0,
3099
+ key: String(derived.scopeKey || ""),
3100
+ ownedKeySubject,
3101
+ ownedCustody: typeof derived.ownedCustody === "string" ? derived.ownedCustody : void 0,
3102
+ keyCommitment: typeof derived.keyCommitment === "string" ? derived.keyCommitment : void 0,
3103
+ principalWrappingPublic: typeof derived.principalWrappingPublic === "string" ? derived.principalWrappingPublic : void 0
3104
+ };
3105
+ }
3106
+ if (!active.includes(ownedKeySubject)) {
3107
+ throw new Error(
3108
+ "this device is waiting for approval \u2014 approve its opaque request on an already-working device"
3109
+ );
3110
+ }
3111
+ const custody = String(leaf(leaves[ownedKeySubject]).ownedCustody || "");
3112
+ if (!custody) throw new Error("the approved device has no owned-data handoff");
3113
+ const opened = JSON.parse(call(eng.ex, "query", {
3114
+ queryBytes: b64Json({
3115
+ op: "cryptoOwnedScopeKey",
3116
+ workspace,
3117
+ principal,
3118
+ authorSecret,
3119
+ scope,
3120
+ ownedCustody: custody,
3121
+ ...recordObject === void 0 ? {} : { recordObject, recordEpoch },
3122
+ ...recordWrap === void 0 ? {} : { recordWrap }
3123
+ })
3124
+ }, eng.STDERR));
3125
+ return {
3126
+ scope: String(opened.scope || scope),
3127
+ epoch: Number(opened.epoch) || 0,
3128
+ key: String(opened.scopeKey || ""),
3129
+ ownedKeySubject,
3130
+ keyCommitment: typeof opened.keyCommitment === "string" ? opened.keyCommitment : void 0,
3131
+ principalWrappingPublic: typeof opened.principalWrappingPublic === "string" ? opened.principalWrappingPublic : void 0
3132
+ };
3133
+ }
3134
+ return {
3135
+ scope: String(derived.scope || scope),
3136
+ epoch: Number(derived.epoch) || 0,
3137
+ key: String(derived.scopeKey || ""),
3138
+ ownedKeySubject,
3139
+ ownedCustody: typeof derived.ownedCustody === "string" ? derived.ownedCustody : void 0,
3140
+ keyCommitment: typeof derived.keyCommitment === "string" ? derived.keyCommitment : void 0,
3141
+ principalWrappingPublic: typeof derived.principalWrappingPublic === "string" ? derived.principalWrappingPublic : void 0
3142
+ };
3143
+ };
3144
+ cryptoOwnedDeviceRequest = (eng, workspace, principal, authorSecret, scope) => String(JSON.parse(call(eng.ex, "query", {
3145
+ queryBytes: b64Json({ op: "cryptoOwnedDeviceRequest", workspace, principal, authorSecret, scope })
3146
+ }, eng.STDERR)).request || "");
3147
+ cryptoOwnedPrepareApproval = (eng, args) => JSON.parse(call(eng.ex, "query", {
3148
+ queryBytes: b64Json({ op: "cryptoOwnedPrepareApproval", ...args })
3149
+ }, eng.STDERR));
3150
+ cryptoOwnedPrepareRecovery = (eng, args) => JSON.parse(call(eng.ex, "query", {
3151
+ queryBytes: b64Json({ op: "cryptoOwnedPrepareRecovery", ...args })
3152
+ }, eng.STDERR));
2730
3153
  cryptoKeygen = (eng) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "cryptoKeygen" }) }, eng.STDERR));
2731
3154
  authorKeygen = (eng) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "authorKeygen" }) }, eng.STDERR));
2732
3155
  eraCertSign = (eng, args) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "eraCertSign", ...args }) }, eng.STDERR));
@@ -2774,7 +3197,8 @@ var init_engine = __esm({
2774
3197
  readPosture = (eng, ws, principal = "", authToken = "", nowUnix = 0) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "readPosture", authToken, nowUnix }), principal, branch: BRANCH }, eng.STDERR));
2775
3198
  count = (eng, ws, countId, groupKey, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "count", countId, groupKey }), principal, branch: BRANCH }, eng.STDERR));
2776
3199
  sum = (eng, ws, sumId, groupKey, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "sum", sumId, groupKey }), principal, branch: BRANCH }, eng.STDERR));
2777
- pendingConflicts = (eng, ws, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "pendingConflicts" }), principal, branch: BRANCH }, eng.STDERR));
3200
+ pendingConflicts = (eng, ws, principal = "", aggregateType = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "pendingConflicts", aggregateType }), principal, branch: BRANCH }, eng.STDERR));
3201
+ pendingConflictSummary = (eng, ws, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "pendingConflictSummary" }), principal, branch: BRANCH }, eng.STDERR));
2778
3202
  spatialBboxParams = (bbox) => {
2779
3203
  const minX = bbox.minX ?? bbox.minLng, minY = bbox.minY ?? bbox.minLat, maxX = bbox.maxX ?? bbox.maxLng, maxY = bbox.maxY ?? bbox.maxLat;
2780
3204
  return { minX, minY, maxX, maxY, minLng: minX, minLat: minY, maxLng: maxX, maxLat: maxY };
@@ -2820,15 +3244,28 @@ async function fetchJsonCached(url, cacheFile) {
2820
3244
  }
2821
3245
  }
2822
3246
  async function fetchRuntime(cloud, kind = "production") {
2823
- const cache = join2(configDir(), "runtime");
3247
+ const cache = process.env["NOMOS_OFFLINE_RUNTIME"] || join2(configDir(), "runtime");
2824
3248
  let wasmBytes;
2825
3249
  const wasmName = kind === "test-mint" ? "holon-testmint.wasm" : "holon.wasm";
2826
3250
  const wasmUrl = kind === "test-mint" ? "holon-testmint.wasm" : "holon.wasm";
2827
3251
  const wasmCache = join2(cache, wasmName);
3252
+ const packagesCache = join2(cache, "packages.json");
3253
+ const refresh = String(process.env["NOMOS_RUNTIME_REFRESH"] || "auto").toLowerCase();
3254
+ if (refresh === "never") {
3255
+ if (!existsSync2(wasmCache) || !existsSync2(packagesCache)) {
3256
+ throw new Error(
3257
+ `offline runtime cache missing at ${cache} (${wasmName} + packages.json); build or warm that exact runtime before running with NOMOS_RUNTIME_REFRESH=never`
3258
+ );
3259
+ }
3260
+ return {
3261
+ wasmBytes: readFileSync(wasmCache),
3262
+ packages: JSON.parse(readFileSync(packagesCache, "utf8"))
3263
+ };
3264
+ }
2828
3265
  const localWasm = process.env["NOMOS_LOCAL_WASM"];
2829
3266
  if (localWasm) {
2830
3267
  const bytes = readFileSync(localWasm);
2831
- const packages2 = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join2(cache, "packages.json"));
3268
+ const packages2 = await fetchJsonCached(`${cloud}/v1/runtime/packages`, packagesCache);
2832
3269
  return { wasmBytes: bytes, packages: packages2 };
2833
3270
  }
2834
3271
  try {
@@ -2841,7 +3278,7 @@ async function fetchRuntime(cloud, kind = "production") {
2841
3278
  if (!existsSync2(wasmCache)) throw e;
2842
3279
  wasmBytes = readFileSync(wasmCache);
2843
3280
  }
2844
- const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join2(cache, "packages.json"));
3281
+ const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`, packagesCache);
2845
3282
  return { wasmBytes, packages };
2846
3283
  }
2847
3284
  function atomicWriteRuntime(target, data) {
@@ -4980,14 +5417,14 @@ function negotiateDeployRoute(caps, opts) {
4980
5417
  if (opts.signed) {
4981
5418
  if (caps.offerLane === false) {
4982
5419
  return {
4983
- fail: `${caps.cloud} does not serve the SIGNED OFFER lane \u2014 POST /v2/workspaces/${opts.ws}/${opts.route.domain}/${opts.route.directiveId} is not routed (the workspace serves no self-describing OpenAPI, so this cloud has no gateway to route a signed offer). This is often a lightweight cloud (e.g. \`githolon cloud dev\`) without the offer gateway.
4984
- \u2192 deploy the OPEN (unsigned) lane instead (drop --as/--principal) if this workspace is unwarranted, or target a cloud that serves the offer gateway.`
5420
+ fail: `${caps.cloud} cannot accept an authenticated deployment for this workspace \u2014 POST /v2/workspaces/${opts.ws}/${opts.route.domain}/${opts.route.directiveId} is not routed because the workspace serves no self-describing gateway.
5421
+ \u2192 target a current Nomos cloud that supports attributed deployment.`
4985
5422
  };
4986
5423
  }
4987
5424
  if (caps.offerLane === true && caps.routesTarget === false) {
4988
5425
  return {
4989
- fail: `${caps.cloud} serves the offer gateway for ${opts.ws}, but its OpenAPI does not name the route ${opts.route.domain}/${opts.route.directiveId} \u2014 the ${opts.route.domain} controller is not installed here, so a signed ${opts.route.directiveId} would 404.
4990
- \u2192 confirm the workspace is born and the controller is deployed, or deploy the OPEN lane (drop --as).`
5426
+ fail: `${caps.cloud} serves the offer gateway for ${opts.ws}, but its OpenAPI does not name the route ${opts.route.domain}/${opts.route.directiveId} \u2014 the ${opts.route.domain} controller is not installed here, so the lifecycle request would 404.
5427
+ \u2192 confirm the workspace is born and its current Nomos controller is installed.`
4991
5428
  };
4992
5429
  }
4993
5430
  if (opts.sealedSizeChars > INLINE_OFFER_CAP_CHARS && caps.stage === false) {
@@ -5065,7 +5502,7 @@ function domainKeysOf(body) {
5065
5502
  function packageHashOf(packageUsda) {
5066
5503
  return createHash2("sha256").update(Buffer.from(packageUsda, "utf8")).digest("hex");
5067
5504
  }
5068
- function resolveDeployPlan(domainKeys, currentLaw2, targetHash, opts = {}) {
5505
+ function resolveDeployPlan(domainKeys, currentLaw2, targetHash) {
5069
5506
  const decisions = [];
5070
5507
  const priors = /* @__PURE__ */ new Set();
5071
5508
  for (const key of domainKeys) {
@@ -5074,21 +5511,18 @@ function resolveDeployPlan(domainKeys, currentLaw2, targetHash, opts = {}) {
5074
5511
  decisions.push({ key, action: "first" });
5075
5512
  } else if (currentHash === targetHash) {
5076
5513
  decisions.push({ key, currentHash, action: "idempotent" });
5077
- } else if (opts.keepBeside === true) {
5078
- decisions.push({ key, currentHash, action: "keep-beside" });
5079
5514
  } else {
5080
- decisions.push({ key, currentHash, action: "supersedes" });
5515
+ decisions.push({ key, currentHash, action: "successor" });
5081
5516
  priors.add(currentHash);
5082
5517
  }
5083
5518
  }
5084
- const replaceCandidates = [...priors];
5519
+ const predecessorCandidates = [...priors];
5085
5520
  const idempotent = domainKeys.length > 0 && decisions.every((d) => d.action === "idempotent");
5086
5521
  return {
5087
5522
  decisions,
5088
- ...replaceCandidates.length > 0 ? { replaces: replaceCandidates[0] } : {},
5089
- retireReplaced: opts.retireReplaced === true,
5523
+ ...predecessorCandidates.length > 0 ? { supersedes: predecessorCandidates[0] } : {},
5090
5524
  idempotent,
5091
- replaceCandidates
5525
+ predecessorCandidates
5092
5526
  };
5093
5527
  }
5094
5528
  function describeDeployPlan(plan, targetHash) {
@@ -5097,10 +5531,8 @@ function describeDeployPlan(plan, targetHash) {
5097
5531
  switch (d.action) {
5098
5532
  case "first":
5099
5533
  return ` ${d.key}: ${t} installed (first)`;
5100
- case "supersedes":
5101
- return ` ${d.key}: ${t} supersedes ${short(d.currentHash)}${plan.retireReplaced ? " (retired)" : ""}`;
5102
- case "keep-beside":
5103
- return ` ${d.key}: ${t} kept beside ${short(d.currentHash)}`;
5534
+ case "successor":
5535
+ return ` ${d.key}: ${t} is the candidate after ${short(d.currentHash)}`;
5104
5536
  case "idempotent":
5105
5537
  return ` ${d.key}: unchanged \u2014 ${t} is already active`;
5106
5538
  }
@@ -5571,7 +6003,7 @@ async function wsCreate(name, opts) {
5571
6003
  return 1;
5572
6004
  }
5573
6005
  out4(`\u2713 createWorkspace '${name}' requested via signed governance offer to ${opts.via} on ${cloud}`);
5574
- out4(` the worker provisions it on first touch; deploy is open (kernel-gated)`);
6006
+ out4(` the worker provisions it on first touch; then run githolon deploy ${name}`);
5575
6007
  out4(` (want the direct birth instead? drop --via: githolon ws create ${name})`);
5576
6008
  return 0;
5577
6009
  }
@@ -5614,7 +6046,7 @@ async function wsCreate(name, opts) {
5614
6046
  }
5615
6047
  if (opts.principal !== void 0) setPrincipal(opts.principal);
5616
6048
  out4(`\u2713 workspace ${name} created on ${cloud}`);
5617
- out4(` deploy is open (kernel-gated): githolon deploy ${name}`);
6049
+ out4(` deploy its first model: githolon deploy ${name}`);
5618
6050
  return 0;
5619
6051
  }
5620
6052
  async function wsStatus(name, opts) {
@@ -5816,14 +6248,18 @@ async function deploy2(ws, opts) {
5816
6248
  try {
5817
6249
  const body = readDeployBody3(file);
5818
6250
  targetHash = packageHashOf2(body.packageUsda);
5819
- plan = resolveDeployPlan2(domainKeysOf2(body), currentLaw2, targetHash, {
5820
- keepBeside: opts.keepBeside === true,
5821
- retireReplaced: opts.retireReplaced === true
5822
- });
5823
- } catch {
5824
- plan = void 0;
5825
- targetHash = void 0;
6251
+ plan = resolveDeployPlan2(domainKeysOf2(body), currentLaw2, targetHash);
6252
+ } catch (error) {
6253
+ err4(`deploy '${ws}' needs the current generated deploy body: ${error.message}`);
6254
+ return 1;
6255
+ }
6256
+ if ((plan?.predecessorCandidates.length ?? 0) > 1) {
6257
+ err4(
6258
+ `deploy '${ws}' refused: this candidate spans ${plan.predecessorCandidates.length} different current models; Nomos will not guess one predecessor. Compile one coherent workspace model and try again.`
6259
+ );
6260
+ return 1;
5826
6261
  }
6262
+ let expectsPending = plan !== void 0 && plan.predecessorCandidates.length === 1 && !plan.idempotent;
5827
6263
  const principal = opts.principal ?? activePrincipal();
5828
6264
  const signed = opts.principal !== void 0 || principal !== void 0 && getDeviceKey(principal) !== void 0;
5829
6265
  let phaseSuffix = "";
@@ -5842,8 +6278,14 @@ async function deploy2(ws, opts) {
5842
6278
  return 1;
5843
6279
  }
5844
6280
  const targetHash2 = createHash3("sha256").update(Buffer.from(packageUsda, "utf8")).digest("hex");
6281
+ if (expectsPending && body.dispositions !== void 0 && body.dispositions !== null) {
6282
+ err4(
6283
+ `deploy '${ws}' refused: successor models cannot carry caller-chosen schema dispositions; evolution must be proved by model.adopt(), not smuggled into deployment`
6284
+ );
6285
+ return 1;
6286
+ }
5845
6287
  const { probeCloudCapabilities: probeCloudCapabilities2, negotiateDeployRoute: negotiateDeployRoute2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
5846
- const route = { domain: "nomos", directiveId: "installDomain" };
6288
+ const route = { domain: "nomos", directiveId: expectsPending ? "stageDomain" : "installDomain" };
5847
6289
  const caps = await probeCloudCapabilities2(cloud, ws, route);
5848
6290
  const refusal = negotiateDeployRoute2(caps, { signed: true, ws, sealedSizeChars: packageUsda.length, route });
5849
6291
  if (refusal !== void 0) {
@@ -5855,17 +6297,16 @@ async function deploy2(ws, opts) {
5855
6297
  let v;
5856
6298
  try {
5857
6299
  const who = resolveGovernancePrincipal2(opts);
5858
- const installPayload2 = {
6300
+ const lifecyclePayload = {
5859
6301
  domainHash: targetHash2,
5860
6302
  packageUsda,
5861
6303
  installedBy: `user:${who}`,
5862
6304
  dependencies: [],
5863
6305
  finalizers: [],
5864
- // REPLACE-CURRENT: supersede the prior install per key (unless --keep-beside / first-install / idempotent).
5865
- ...plan?.replaces !== void 0 ? { replaces: plan.replaces, retireReplaced: plan.retireReplaced } : {},
5866
- ...body.dispositions !== void 0 && body.dispositions !== null ? { dispositions: body.dispositions } : {}
6306
+ ...expectsPending && plan?.supersedes !== void 0 ? { supersedes: plan.supersedes } : {},
6307
+ ...!expectsPending && body.dispositions !== void 0 && body.dispositions !== null ? { dispositions: body.dispositions } : {}
5867
6308
  };
5868
- v = await signAndPostOffer2(ws, "nomos", "installDomain", installPayload2, opts);
6309
+ v = await signAndPostOffer2(ws, "nomos", route.directiveId, lifecyclePayload, opts);
5869
6310
  } catch (e) {
5870
6311
  err4(e.message);
5871
6312
  return 1;
@@ -5887,7 +6328,7 @@ async function deploy2(ws, opts) {
5887
6328
  const txt = d ? JSON.stringify(d) : "no body";
5888
6329
  err4(`deploy '${ws}' failed (${r.status}): ${txt}`);
5889
6330
  if (r.status === 401 || r.status === 403 || r.status === 422 || /warrant|signed|signer|unsigned|authorship|forged|relation|installDomain/i.test(txt)) {
5890
- err4(` this workspace looks WARRANTED \u2014 re-run with --as <principal> to author a SIGNED install offer`);
6331
+ err4(` this workspace looks WARRANTED \u2014 re-run with --as <principal> to author a signed lifecycle offer`);
5891
6332
  err4(` (import the admin signing key first if needed: githolon key import --principal <uid> --secret <key>)`);
5892
6333
  }
5893
6334
  return 1;
@@ -5895,6 +6336,7 @@ async function deploy2(ws, opts) {
5895
6336
  const phase = d.installation?.[0]?.data?.["status.phase"];
5896
6337
  phaseSuffix = phase === "Active" ? " (active)" : typeof phase === "string" ? ` (${phase.toLowerCase()})` : "";
5897
6338
  deployedHash = typeof d.domainHash === "string" ? d.domainHash : void 0;
6339
+ expectsPending = d.staged === true || expectsPending;
5898
6340
  }
5899
6341
  const after = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((ar) => ar.json()).then((ad) => ad.ok === true ? ad : {}).catch(() => ({}));
5900
6342
  const currentHashes = new Set(Object.values(after.currentLaw ?? {}));
@@ -5903,13 +6345,28 @@ async function deploy2(ws, opts) {
5903
6345
  const emitPlan = () => {
5904
6346
  if (plan === void 0 || targetHash === void 0 || plan.decisions.length === 0) return;
5905
6347
  for (const line of describeDeployPlan2(plan, targetHash)) out4(line);
5906
- if (plan.replaceCandidates.length > 1) {
5907
- out4(` \u26A0 the package's keys were active on ${plan.replaceCandidates.length} different installs \u2014 installDomain supersedes one (${plan.replaces.slice(0, 16)}\u2026); this deploy is active for every key.`);
5908
- }
5909
- if (!signed && plan.replaces !== void 0 && !opts.keepBeside) {
5910
- out4(` note: the OPEN (unsigned) lane installs beside \u2014 the new law is active, and the prior install is effectively superseded but not marked Superseded. Sign with --as <uid> to record the supersession.`);
5911
- }
5912
6348
  };
6349
+ if (expectsPending && deployedHash !== void 0) {
6350
+ const row = (after.domains ?? []).find((x) => x.domainHash === deployedHash);
6351
+ const unchanged = Object.entries(currentLaw2).every(([key, hash]) => after.currentLaw?.[key] === hash);
6352
+ if (row?.phase !== "Pending" || !unchanged || isCurrent) {
6353
+ err4(`deploy '${ws}' refused its own outcome: a successor must be Pending while the current model keeps serving`);
6354
+ err4(` observed phase=${row?.phase ?? "missing"}; current law ${unchanged && !isCurrent ? "unchanged" : "changed"}`);
6355
+ return 2;
6356
+ }
6357
+ if (beforeDurableHead !== void 0) {
6358
+ const durableHead = await waitForDurableMainAdvance(cloud, ws, beforeDurableHead);
6359
+ if (durableHead === void 0) {
6360
+ err4(`deploy '${ws}' staged the candidate, but canonical Git main did not advance within 30000ms`);
6361
+ return 2;
6362
+ }
6363
+ }
6364
+ out4(`\u2713 staged ${fileName} \u2192 ${ws} (pending)`);
6365
+ if (plan?.supersedes) out4(` current model ${plan.supersedes.slice(0, 16)}\u2026 keeps serving`);
6366
+ out4(` candidate ${deployedHash.slice(0, 16)}\u2026 is in custody but cannot serve yet`);
6367
+ out4(` next: use the candidate generated client and call model.adopt()`);
6368
+ return 0;
6369
+ }
5913
6370
  if (deployedHash !== void 0 && isCurrent) {
5914
6371
  const wasCurrentBefore = Object.values(currentLaw2).includes(deployedHash);
5915
6372
  if (!wasCurrentBefore && beforeDurableHead !== void 0) {
@@ -6316,6 +6773,39 @@ async function runOfflineLegs(eng, ws, lawHash, legs, frameworkHash, deployUsda)
6316
6773
  }
6317
6774
  return done(true, lines);
6318
6775
  }
6776
+ function sealOwnedBusinessBirth(eng, payload, encryption, authorSecret, principal) {
6777
+ const key = payload[encryption.keyField];
6778
+ const digest = createHash4("sha256").update(`${encryption.namespace}${String(key)}`).digest("hex");
6779
+ const childWorkspace = `${encryption.namespace}-${digest.slice(0, 40)}`;
6780
+ const scopeKey = cryptoOwnedScopeKey(
6781
+ eng,
6782
+ childWorkspace,
6783
+ authorSecret,
6784
+ encryption.scope,
6785
+ principal,
6786
+ false
6787
+ );
6788
+ const sealed = {
6789
+ [OWNED_KEY_PAYLOAD_FIELD]: scopeKey.ownedKeySubject,
6790
+ ...scopeKey.ownedCustody ? { [OWNED_CUSTODY_PAYLOAD_FIELD]: scopeKey.ownedCustody } : {}
6791
+ };
6792
+ for (const [field, slot] of Object.entries(encryption.fields)) {
6793
+ const value = payload[field];
6794
+ if (value === void 0 || value === null) continue;
6795
+ sealed[field] = cryptoSealField(eng, {
6796
+ scopeKey: scopeKey.key,
6797
+ scope: encryption.scope,
6798
+ epoch: scopeKey.epoch,
6799
+ aggSid: slot.aggSid,
6800
+ fieldSid: slot.fieldSid,
6801
+ plaintext: String(value)
6802
+ });
6803
+ }
6804
+ payload[BUSINESS_BIRTH_SEED_PAYLOAD_FIELD] = JSON.stringify(sealed);
6805
+ payload[OWNED_KEY_PAYLOAD_FIELD] = scopeKey.ownedKeySubject;
6806
+ if (scopeKey.ownedCustody) payload[OWNED_CUSTODY_PAYLOAD_FIELD] = scopeKey.ownedCustody;
6807
+ return { childWorkspace, scopeKey };
6808
+ }
6319
6809
  function warrantParentForBirth(eng, parentWs, parentDomain, lawHash, genesisOwner, birthActor, dev2, stamp) {
6320
6810
  const authorFrameworkAs = (directiveId, payload, actor) => {
6321
6811
  for (const dom of [parentDomain, "nomos"]) {
@@ -6382,7 +6872,10 @@ function warrantParentForBirth(eng, parentWs, parentDomain, lawHash, genesisOwne
6382
6872
  return { ok: true, attestationVerified: hasDelegation && hasParentAttestation };
6383
6873
  }
6384
6874
  function attestedBirthOffer(eng, parentWs, parentDomain, directiveId, payloadNoCert, lawHash, founder, dev2, verified = true) {
6385
- const probe = author(eng, parentWs, parentDomain, directiveId, payloadNoCert, lawHash, { actor: founder });
6875
+ const probe = author(eng, parentWs, parentDomain, directiveId, payloadNoCert, lawHash, {
6876
+ actor: founder,
6877
+ authorSecret: dev2.secret
6878
+ });
6386
6879
  if (probe.ok !== false) {
6387
6880
  return { res: probe };
6388
6881
  }
@@ -6489,6 +6982,7 @@ async function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
6489
6982
  return done(true);
6490
6983
  }
6491
6984
  }
6985
+ const ownedBirth = cb.businessEncryption !== void 0 ? sealOwnedBusinessBirth(eng, payload, cb.businessEncryption, dev2.secret, founder) : void 0;
6492
6986
  const { res } = attestedBirthOffer(
6493
6987
  eng,
6494
6988
  parentWs,
@@ -6603,10 +7097,29 @@ async function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
6603
7097
  return done(false, `the recovered child ${childWs} FAILED verify_chain from its own custody: ${recoveredVerify.error ?? "invalid"}`);
6604
7098
  }
6605
7099
  if (cb.childSeedInstanceId !== void 0) {
6606
- const recoveredRows = qById(eng, childWs, cb.childSeedInstanceId);
7100
+ const recoveredRows = qById(
7101
+ eng,
7102
+ childWs,
7103
+ cb.childSeedInstanceId,
7104
+ ownedBirth === void 0 ? "" : founder,
7105
+ ownedBirth === void 0 ? [] : [ownedBirth.scopeKey]
7106
+ );
6607
7107
  if (recoveredRows.length < 1) {
6608
7108
  return done(false, `the recovered child ${childWs} verified but its business root ${cb.childSeedInstanceId} was missing`);
6609
7109
  }
7110
+ if (ownedBirth !== void 0 && cb.businessEncryption !== void 0) {
7111
+ if (childWs !== ownedBirth.childWorkspace) {
7112
+ return done(false, `the born child '${childWs}' differs from the deterministic owned-custody address '${ownedBirth.childWorkspace}'`);
7113
+ }
7114
+ const data = recoveredRows[0]?.data ?? {};
7115
+ for (const field of Object.keys(cb.businessEncryption.fields)) {
7116
+ const expected = cb.birthPayload[field];
7117
+ if (expected !== void 0 && data[field] !== expected) {
7118
+ return done(false, `the recovered owned ${cb.businessAggregateId ?? "aggregate"} did not decrypt '${field}' back to its business fact`);
7119
+ }
7120
+ }
7121
+ lines.push(`\u2713 owned custody \u2014 recovered business facts decrypted under the creator's framework-managed device key`);
7122
+ }
6610
7123
  }
6611
7124
  if (cb.businessAggregateId !== void 0) {
6612
7125
  lines.push(`\u2713 persistence + recovery \u2014 evicted, restored from durable custody, and ${cb.businessAggregateId} read back`);
@@ -6870,7 +7383,7 @@ OFFLINE PROOF GREEN (${Math.round(v.ms)}ms) \u2014 the same legs rerun on every
6870
7383
  console.log(`Run the same generated business proof against Nomos Cloud when ready: githolon proof --live`);
6871
7384
  return 0;
6872
7385
  }
6873
- var keyHashOf, OFFLINE_PROOF_VERIFIER;
7386
+ var keyHashOf, OFFLINE_PROOF_VERIFIER, OWNED_KEY_PAYLOAD_FIELD, OWNED_CUSTODY_PAYLOAD_FIELD, BUSINESS_BIRTH_SEED_PAYLOAD_FIELD;
6874
7387
  var init_proof_offline = __esm({
6875
7388
  "src/proof_offline.ts"() {
6876
7389
  "use strict";
@@ -6878,6 +7391,9 @@ var init_proof_offline = __esm({
6878
7391
  init_local_holon();
6879
7392
  keyHashOf = (pubB64) => createHash4("sha256").update(Buffer.from(pubB64, "base64")).digest("hex");
6880
7393
  OFFLINE_PROOF_VERIFIER = "nomos:test-mint";
7394
+ OWNED_KEY_PAYLOAD_FIELD = "__nomos_owned_device_key_subject_7c1f5e";
7395
+ OWNED_CUSTODY_PAYLOAD_FIELD = "__nomos_owned_device_custody_7c1f5e";
7396
+ BUSINESS_BIRTH_SEED_PAYLOAD_FIELD = "__nomos_birth_seed_payload_7c1f5e";
6881
7397
  }
6882
7398
  });
6883
7399
 
@@ -8694,7 +9210,7 @@ export const ${n.pascal} = aggregate("${n.pascal}", {
8694
9210
  status: t.enum(${n.statusesConst}).merge(Lww),
8695
9211
  tags: t.set(t.string()).merge(AddWins),
8696
9212
  customFields: t.map(t.string()).merge(MapOf(Lww)),
8697
- });`;
9213
+ }).public();`;
8698
9214
  }
8699
9215
  function domainTemplate(raw) {
8700
9216
  const n = namesFor(raw);
@@ -8766,7 +9282,7 @@ import { z, aggregate, directive, t, Conflict, set } from "@githolon/dsl";
8766
9282
  // Stub target \u2014 in a real domain, import the existing aggregate handle instead.
8767
9283
  export const ${n.pascal} = aggregate("${n.pascal}", {
8768
9284
  label: t.string().merge(Conflict),
8769
- });
9285
+ }).public();
8770
9286
 
8771
9287
  /** ${n.camel} \u2014 .mutates the target aggregate. */
8772
9288
  export const ${n.camel} = directive("${n.camel}")
@@ -9481,21 +9997,19 @@ var HELP = {
9481
9997
  examples: ["githolon revoke creation 32c7100e-\u2026", "githolon revoke admin 32c7100e-\u2026"]
9482
9998
  },
9483
9999
  deploy: {
9484
- usage: "githolon deploy [<ws>] [--as <uid>] [--keep-beside] [--retire-replaced] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
9485
- what: "Deploy build/*.deploy.json \u2014 kernel-gated, no host secret. DEFAULT = REPLACE-CURRENT: for each domain\nkey in the package it resolves the workspace's active install (the holon's existing `currentLaw` map)\nand passes it as installDomain's `replaces`, so the old install becomes superseded for that key.\nRe-deploying the SAME hash stays idempotent (never self-superseded).\nTWO lanes, picked automatically:\n\u2022 SIGNED (a WARRANTED ws, or when --as / the active `githolon use` principal has a signing key on\n file): signs a `nomos/installDomain` offer (carrying `replaces`) with that key and relays it; the\n kernel re-verifies the signature + judges the installDomain relation. Required for warranted platforms.\n\u2022 OPEN (unwarranted ws / no key): the open POST /domains installs beside. The new law becomes active,\n but the prior install is only effectively superseded unless you sign with --as to record Superseded.\nAfter the POST it CONFIRMS the deployed hash is active (exit 2 if committed-but-not-active) and prints\nwhat moved, per key. With no <ws>, the project's `workspace` binding resolves it.\nLifecycle vocabulary: active = served now; superseded = replayable, not served; retired = explicitly withdrawn, replayable, not served.",
10000
+ usage: "githolon deploy [<ws>] [--as <uid>] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
10001
+ what: "Put the compiled model in the workspace's verified custody. There is one lifecycle: the first\nmodel becomes active so a blank application can start; every successor stays Pending while the current\nmodel keeps serving. The candidate generated client then runs the exact-history proof through\n`model.adopt()` and either adopts immediately or returns a business-readable decision requiring\n`decision.accept(reason)`. Deployment cannot choose a replacement mode or bypass that proof.\nRe-deploying the same content hash is idempotent. With no <ws>, the project's workspace binding resolves it.",
9486
10002
  flags: [
9487
- ["--as <uid>", "deploy as this principal via the SIGNED lane (needs its key \u2014 githolon key import; else githolon use)"],
9488
- ["--keep-beside", "install beside the prior law (the old multi-version default; prior becomes effectively superseded by recency)"],
9489
- ["--retire-replaced", "supersede via Retired \u2014 the prior install is explicitly withdrawn"],
10003
+ ["--as <uid>", "attribute the deployment to this principal (needs its enrolled signing key)"],
9490
10004
  ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
9491
10005
  ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
9492
10006
  ["--cloud <url>", "target cloud"]
9493
10007
  ],
9494
- examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon deploy co2-platform --as <admin-uid>", "githolon deploy my-ws --keep-beside", "githolon deploy my-ws --retire-replaced"]
10008
+ examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon deploy co2-platform --as <admin-uid>"]
9495
10009
  },
9496
10010
  domains: {
9497
10011
  usage: "githolon domains <status <ws> [--explain-current-interface] | retire <ws> <hashOrKey> [--with <hashOrKey>]>",
9498
- what: "The INSTALL-LIFECYCLE lane over a workspace's law.\n\u2022 status: per domain KEY, the active hash, then the superseded/retired lineage behind it.\n Lifecycle vocabulary: active = served now; superseded = replaced by a newer install for the same domain\n key, replayable, not served; retired = explicitly withdrawn, replayable, not served.\n --explain-current-interface spells out why active reads/dispatch use only that active map.\n\u2022 retire: mark an install Retired via a signed installDomain offer. The framework retires only as the\n consequence of installing a SUCCESSOR \u2014 by default the local compiled package (build/*.deploy.json),\n or --with <hashOrKey> names an already-installed successor. A key the successor does not cover is left\n with NO active law \u2014 WARNED loudly, never blocked. A key resolves to its active hash; a 64-hex arg is\n taken as the install hash.",
10012
+ what: "Framework lifecycle inspection and explicit withdrawal.\n\u2022 status: per domain KEY, the active hash, then the superseded/retired lineage behind it.\n Lifecycle vocabulary: active = served now; superseded = replaced by a newer install for the same domain\n key, replayable, not served; retired = explicitly withdrawn, replayable, not served.\n --explain-current-interface spells out why active reads/dispatch use only that active map.\n\u2022 retire: mark an install Retired via a signed installDomain offer. The framework retires only as the\n consequence of installing a SUCCESSOR \u2014 by default the local compiled package (build/*.deploy.json),\n or --with <hashOrKey> names an already-installed successor. A key the successor does not cover is left\n with NO active law \u2014 WARNED loudly, never blocked. A key resolves to its active hash; a 64-hex arg is\n taken as the install hash.",
9499
10013
  flags: [
9500
10014
  ["--explain-current-interface", "status: also explain the frontier resolution (why the interface is what it is)"],
9501
10015
  ["--with <hashOrKey>", "retire: the retiring successor package \u2014 an already-installed hash or served key"],
@@ -9506,7 +10020,7 @@ var HELP = {
9506
10020
  },
9507
10021
  secret: {
9508
10022
  usage: "githolon secret <set <ws> <push-password> | rotate <ws>> [--cloud <url>]",
9509
- what: "The git push-password store (~/.holon/credentials.json, 0600) for authenticated git sync to the\ncloud host. The open deploy lane uses no secret. `set` imports a push password you already hold;\n`rotate` is RETIRED (the host holds no ownership credential to rotate \u2014 410). (There is no\npush-to-create birth: a workspace is born by its parent authoring its birth.)",
10023
+ what: "The git push-password store (~/.holon/credentials.json, 0600) for authenticated git sync to the\ncloud host. Deployment has no host secret. `set` imports a push password you already hold;\n`rotate` is RETIRED (the host holds no ownership credential to rotate \u2014 410). (There is no\npush-to-create birth: a workspace is born by its parent authoring its birth.)",
9510
10024
  examples: ["githolon secret set my-ws holon_v1_\u2026"]
9511
10025
  },
9512
10026
  git: {
@@ -10052,15 +10566,13 @@ function cloudArgs(rest) {
10052
10566
  const pos = [];
10053
10567
  const opts = {};
10054
10568
  const VALUE_FLAGS = ["--cloud", "--principal", "--as", "--file", "--target", "--parent", "--via", "--max", "--label", "--secret", "--payload", "--payload-file", "--save", "--format", "--with", "--out", "--reason", "--oid", "--seed-secret", "--gcp-secret", "--controller-hash", "--plan", "--depth", "--domain", "--directive"];
10055
- const BOOL_FLAGS = ["--sha256", "--stamp", "--yes", "--clear", "--keep-beside", "--retire-replaced", "--explain-current-interface", "--dry-run", "--json", "--full"];
10569
+ const BOOL_FLAGS = ["--sha256", "--stamp", "--yes", "--clear", "--explain-current-interface", "--dry-run", "--json", "--full"];
10056
10570
  for (let i = 0; i < rest.length; i++) {
10057
10571
  const a = rest[i];
10058
10572
  if (BOOL_FLAGS.includes(a)) {
10059
10573
  if (a === "--sha256") opts.sha256 = true;
10060
10574
  else if (a === "--stamp") opts.stamp = true;
10061
10575
  else if (a === "--clear") opts.clear = true;
10062
- else if (a === "--keep-beside") opts.keepBeside = true;
10063
- else if (a === "--retire-replaced") opts.retireReplaced = true;
10064
10576
  else if (a === "--explain-current-interface") opts.explain = true;
10065
10577
  else if (a === "--dry-run") opts.dryRun = true;
10066
10578
  else if (a === "--json") opts.json = true;