@volter/world-core 2.0.32 → 2.0.34

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.
@@ -141,6 +141,15 @@ export type Changeset = {
141
141
  /** The author's own words for the changeset (git's commit message) — what `volter changeset -m`
142
142
  * records. Outside the hash, like the narration; a reviewer reads both. */
143
143
  message?: string;
144
+ /** Who cut it, as the World names a caller (`key:<name>`, `key:<name> for:<person>`, `person:<who>`, `machine`, `person`,
145
+ * `token`); the command on this machine records its signed-in person, and is absent when no one is signed in there.
146
+ * Outside the hash, as approvals are (docs/contributing/architecture.md, "Who acted is recorded"). */
147
+ cutBy?: string;
148
+ /** The person accountable for it: the cutting grant's person, else the pusher's person when it lands in a shared World
149
+ * with none. Outside the hash. */
150
+ owner?: string;
151
+ /** Who pushed it into the World that keeps this copy, a caller as `cutBy` names one. Outside the hash. */
152
+ pushedBy?: string;
144
153
  /** v3 — set by `rebaseChangeset`: the hash this object was rebased from, and when. Outside the hash. */
145
154
  rebasedFrom?: {
146
155
  contentHash: string;
@@ -212,6 +221,8 @@ export declare function buildChangeset(opts: {
212
221
  createdAt?: string;
213
222
  message?: string;
214
223
  cut?: Record<string, HistoryReference>;
224
+ cutBy?: string;
225
+ owner?: string;
215
226
  }): Changeset;
216
227
  /** Fill the lifecycle fields a changeset authored before v1 lacks on disk, without touching
217
228
  * its hash (absent and empty `verifiers` hash identically by construction). */
@@ -329,6 +340,9 @@ export type ChangesetReadiness = {
329
340
  contentHash: string;
330
341
  /** stored hash matches the body */
331
342
  hashOk: boolean;
343
+ /** the different people whose approvals bind to the current hash, and how many this World requires */
344
+ approvers: number;
345
+ required: number;
332
346
  /** a verification exists, binds to the current hash, and every verifier passed */
333
347
  verified: boolean;
334
348
  verification: ChangesetVerification | null;
@@ -340,7 +354,13 @@ export type ChangesetReadiness = {
340
354
  /** empty exactly when ready */
341
355
  reasons: string[];
342
356
  };
343
- export declare function changesetReadiness(changeset: Changeset): ChangesetReadiness;
357
+ /** Ready to apply: hashed as authored, verified against its current contents, and approved for them by `rules.approvals`
358
+ * different people (the World's changeset rules; one when it names none). */
359
+ /** Whom an approval counts for: the person a key acts for (`key:<name> for:<person>`), else the principal itself. */
360
+ export declare function approverOf(principal: string): string;
361
+ export declare function changesetReadiness(changeset: Changeset, rules?: {
362
+ approvals?: number;
363
+ }): ChangesetReadiness;
344
364
  /** The single honest line `changeset status` prints. No push here — v2's job; this line only
345
365
  * tells the truth about the object. */
346
366
  export declare function formatChangesetStatus(readiness: ChangesetReadiness): string;
Binary file
@@ -41,7 +41,7 @@ export { forkTwin, isFork, readForkMeta, } from './fork.js';
41
41
  export type { ForkMeta } from './fork.js';
42
42
  export { appendAction, appendTransactionCommit, checkPrecondition, confirmAction, listActions, listTransactionCommits, pendingActions, pushablePendingActions, isTwinBookkeeping, pendingTransactionCommits, projectResources, projectOwnerResources, OwnerStoreAmbiguousError, revertAction, TwinActionPreconditionError, subjectAliases, resolveSubjectId, runWithCorrelationId, currentCorrelationId } from './actions.js';
43
43
  export type { ActionProjection, ProjectedDelivery, ProjectedResource, ProjectedResourcePatch, ProjectedResourceRef, TwinAction, TwinActionOp, TwinActionPrecondition, TwinActionPreconditionOp, TwinActionRevertSpec, TwinTransactionCommit, TwinTransactionCommitOp, TwinTransactionPrecondition, TwinTransactionRevertSpec, } from './actions.js';
44
- export { approveChangeset, assertMarkerBelongsTo, assertSafeChangesetName, assertValidVerifiers, buildChangeset, captureMarker, changesetContentHash, changesetHashMatches, changesetReadiness, CHANGESET_KIND, diffLedgers, formatApplication, formatChangeset, formatRebaseReport, narrateActions, narrationDrift, rebaseChangeset, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, MARKER_KIND, normalizeChangeset, parseVerifierExpression, replayChangeset, runChangesetVerifiers, summarizeByVendor, twinWriteShape, withApplication, withVerification, worldBootMarker, WORLD_BOOT_MARKER_ID, } from './changeset.js';
44
+ export { approveChangeset, assertMarkerBelongsTo, assertSafeChangesetName, assertValidVerifiers, buildChangeset, captureMarker, changesetContentHash, changesetHashMatches, changesetReadiness, approverOf, CHANGESET_KIND, diffLedgers, formatApplication, formatChangeset, formatRebaseReport, narrateActions, narrationDrift, rebaseChangeset, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, MARKER_KIND, normalizeChangeset, parseVerifierExpression, replayChangeset, runChangesetVerifiers, summarizeByVendor, twinWriteShape, withApplication, withVerification, worldBootMarker, WORLD_BOOT_MARKER_ID, } from './changeset.js';
45
45
  export type { ApplyReceipt, RebaseActionResult, RebaseReport, RebaseTarget, Changeset, ChangesetAction, ChangesetApplication, CompensationEntry, ChangesetApproval, ChangesetReadiness, ChangesetVerification, ChangesetVerifier, ChangesetVerifierResult, LedgerDelta, LedgerPosition, LedgerRef, ReplayActionResult, ReplayReport, ReplayTarget, VendorSummary, WorldMarker, } from './changeset.js';
46
46
  export { getActivePackAssets, setActivePackAssets, packAsset, assetContentType, confinedAssetPath, LocalPackAssets, BindingPackAssets, type PackAssetStore } from './pack-assets.js';
47
47
  export * as git from './git/index.js';
package/dist/src/index.js CHANGED
@@ -66,7 +66,7 @@ export { appendAction, appendTransactionCommit, checkPrecondition, confirmAction
66
66
  // the content-addressed, replayable changeset over that delta. State-level by construction
67
67
  // (it reads the same per-service action ledgers `actions.ts` writes); `volter-world` supplies
68
68
  // world discovery and thin verbs on top.
69
- export { approveChangeset, assertMarkerBelongsTo, assertSafeChangesetName, assertValidVerifiers, buildChangeset, captureMarker, changesetContentHash, changesetHashMatches, changesetReadiness, CHANGESET_KIND, diffLedgers, formatApplication, formatChangeset, formatRebaseReport, narrateActions, narrationDrift, rebaseChangeset, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, MARKER_KIND, normalizeChangeset, parseVerifierExpression, replayChangeset, runChangesetVerifiers, summarizeByVendor, twinWriteShape, withApplication, withVerification, worldBootMarker, WORLD_BOOT_MARKER_ID, } from "./changeset.js";
69
+ export { approveChangeset, assertMarkerBelongsTo, assertSafeChangesetName, assertValidVerifiers, buildChangeset, captureMarker, changesetContentHash, changesetHashMatches, changesetReadiness, approverOf, CHANGESET_KIND, diffLedgers, formatApplication, formatChangeset, formatRebaseReport, narrateActions, narrationDrift, rebaseChangeset, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, MARKER_KIND, normalizeChangeset, parseVerifierExpression, replayChangeset, runChangesetVerifiers, summarizeByVendor, twinWriteShape, withApplication, withVerification, worldBootMarker, WORLD_BOOT_MARKER_ID, } from "./changeset.js";
70
70
  export { getActivePackAssets, setActivePackAssets, packAsset, assetContentType, confinedAssetPath, LocalPackAssets, BindingPackAssets } from "./pack-assets.js";
71
71
  // The git plane (contract "The git plane is a kernel library"): objects, packs, refs, smart HTTP.
72
72
  export * as git from "./git/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "2.0.32",
3
+ "version": "2.0.34",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
package/src/changeset.ts CHANGED
@@ -210,6 +210,15 @@ export type Changeset = {
210
210
  /** The author's own words for the changeset (git's commit message) — what `volter changeset -m`
211
211
  * records. Outside the hash, like the narration; a reviewer reads both. */
212
212
  message?: string;
213
+ /** Who cut it, as the World names a caller (`key:<name>`, `key:<name> for:<person>`, `person:<who>`, `machine`, `person`,
214
+ * `token`); the command on this machine records its signed-in person, and is absent when no one is signed in there.
215
+ * Outside the hash, as approvals are (docs/contributing/architecture.md, "Who acted is recorded"). */
216
+ cutBy?: string;
217
+ /** The person accountable for it: the cutting grant's person, else the pusher's person when it lands in a shared World
218
+ * with none. Outside the hash. */
219
+ owner?: string;
220
+ /** Who pushed it into the World that keeps this copy, a caller as `cutBy` names one. Outside the hash. */
221
+ pushedBy?: string;
213
222
  /** v3 — set by `rebaseChangeset`: the hash this object was rebased from, and when. Outside the hash. */
214
223
  rebasedFrom?: { contentHash: string; at: string };
215
224
  /** PROTOCOL 2 — where the parent log stood when this was cut, per state service (its entry count):
@@ -454,7 +463,7 @@ export function parseVerifierExpression(expression: string, id: string): Changes
454
463
  }
455
464
 
456
465
  /** Freeze a delta into a named, content-addressed changeset. */
457
- export function buildChangeset(opts: { name: string; world: string; base: string; actions: ChangesetAction[]; verifiers?: ChangesetVerifier[]; createdAt?: string; message?: string; cut?: Record<string, HistoryReference> }): Changeset {
466
+ export function buildChangeset(opts: { name: string; world: string; base: string; actions: ChangesetAction[]; verifiers?: ChangesetVerifier[]; createdAt?: string; message?: string; cut?: Record<string, HistoryReference>; cutBy?: string; owner?: string }): Changeset {
458
467
  assertSafeChangesetName(opts.name);
459
468
  const verifiers = assertValidVerifiers(opts.verifiers ?? []);
460
469
  const core = { id: `chg_${opts.name}`, world: opts.world, base: opts.base, actions: opts.actions, verifiers, ...(opts.cut === undefined ? {} : { cut: opts.cut }) };
@@ -468,6 +477,8 @@ export function buildChangeset(opts: { name: string; world: string; base: string
468
477
  applied: null,
469
478
  narration: narrateActions(opts.actions),
470
479
  ...(opts.message === undefined ? {} : { message: opts.message }),
480
+ ...(opts.cutBy === undefined ? {} : { cutBy: opts.cutBy }),
481
+ ...(opts.owner === undefined ? {} : { owner: opts.owner }),
471
482
  contentHash: changesetContentHash(core),
472
483
  };
473
484
  }
@@ -509,6 +520,9 @@ function renderVerificationLines(changeset: Changeset): string[] {
509
520
  lines.push(` FAIL ${result.id}: ${renderAssert(result)} — actual ${result.actual === undefined ? 'undefined' : JSON.stringify(result.actual)}`);
510
521
  }
511
522
  }
523
+ // who: cut by, owned by, pushed by (each only when recorded)
524
+ const who = [changeset.cutBy ? `cut by ${changeset.cutBy}` : null, changeset.owner ? `owned by ${changeset.owner}` : null, changeset.pushedBy ? `pushed by ${changeset.pushedBy}` : null].filter(Boolean);
525
+ if (who.length) lines.push(` ${who.join(' · ')}`);
512
526
  if (changeset.approvals.length === 0) {
513
527
  lines.push(' approvals: none');
514
528
  } else {
@@ -790,6 +804,9 @@ export type ChangesetReadiness = {
790
804
  contentHash: string;
791
805
  /** stored hash matches the body */
792
806
  hashOk: boolean;
807
+ /** the different people whose approvals bind to the current hash, and how many this World requires */
808
+ approvers: number;
809
+ required: number;
793
810
  /** a verification exists, binds to the current hash, and every verifier passed */
794
811
  verified: boolean;
795
812
  verification: ChangesetVerification | null;
@@ -802,7 +819,14 @@ export type ChangesetReadiness = {
802
819
  reasons: string[];
803
820
  };
804
821
 
805
- export function changesetReadiness(changeset: Changeset): ChangesetReadiness {
822
+ /** Ready to apply: hashed as authored, verified against its current contents, and approved for them by `rules.approvals`
823
+ * different people (the World's changeset rules; one when it names none). */
824
+ /** Whom an approval counts for: the person a key acts for (`key:<name> for:<person>`), else the principal itself. */
825
+ export function approverOf(principal: string): string {
826
+ const at = principal.indexOf(' for:');
827
+ return principal.startsWith('key:') && at > 0 ? principal.slice(at + ' for:'.length) : principal;
828
+ }
829
+ export function changesetReadiness(changeset: Changeset, rules: { approvals?: number } = {}): ChangesetReadiness {
806
830
  const bodyHash = changesetContentHash(changeset);
807
831
  const hashOk = changeset.contentHash === bodyHash;
808
832
  const verification = changeset.verification;
@@ -820,8 +844,13 @@ export function changesetReadiness(changeset: Changeset): ChangesetReadiness {
820
844
  const failed = verification.results.filter((result) => !result.passed);
821
845
  reasons.push(`verification FAILED: ${failed.map((result) => result.id).join(', ')} (${failed.length} of ${verification.results.length})`);
822
846
  }
823
- if (binding.length === 0) {
824
- reasons.push(changeset.approvals.length ? 'no approval binds to the current body hash' : 'no approvals — run `changeset approve --as <principal>`');
847
+ const required = rules.approvals ?? 1;
848
+ // different people: a key's approval (`key:<name> for:<person>`) is its person's, so a person and their agent are one
849
+ const approvers = new Set(binding.map((approval) => approverOf(approval.principal))).size;
850
+ if (approvers < required) {
851
+ reasons.push(binding.length === 0
852
+ ? (changeset.approvals.length ? 'no approval binds to the current body hash' : 'no approvals — run `changeset approve --as <principal>`')
853
+ : `${approvers} of the ${required} approvals this World requires`);
825
854
  }
826
855
 
827
856
  const verified = Boolean(hashOk && verification && verification.contentHash === changeset.contentHash && verification.passed);
@@ -830,6 +859,8 @@ export function changesetReadiness(changeset: Changeset): ChangesetReadiness {
830
859
  world: changeset.world,
831
860
  contentHash: changeset.contentHash,
832
861
  hashOk,
862
+ approvers,
863
+ required,
833
864
  verified,
834
865
  verification,
835
866
  approvals: changeset.approvals.length,
package/src/index.ts CHANGED
@@ -237,6 +237,7 @@ export {
237
237
  changesetContentHash,
238
238
  changesetHashMatches,
239
239
  changesetReadiness,
240
+ approverOf,
240
241
  CHANGESET_KIND,
241
242
  diffLedgers,
242
243
  formatApplication,