@warmhub/cli 0.93.0 → 0.95.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.
Files changed (2) hide show
  1. package/dist/wh.js +866 -507
  2. package/package.json +1 -1
package/dist/wh.js CHANGED
@@ -19392,10 +19392,13 @@ var ALL_BUILTIN_SHAPE_DEFS = {
19392
19392
  // ../../packages/rules/src/client-flags.ts
19393
19393
  var CLIENT_FLAGS_HEADER = "X-WarmHub-Client-Flags";
19394
19394
  var CLIENT_FLAG_COMPATIBILITY_OVERRIDE = "compatibility-override";
19395
+ var CLIENT_FLAG_EDGE_CACHE_OPT_IN = "edge-cache-opt-in";
19395
19396
  var KNOWN_CLIENT_FLAGS = new Set([
19396
- CLIENT_FLAG_COMPATIBILITY_OVERRIDE
19397
+ CLIENT_FLAG_COMPATIBILITY_OVERRIDE,
19398
+ CLIENT_FLAG_EDGE_CACHE_OPT_IN
19397
19399
  ]);
19398
19400
  var CLIENT_FLAG_TOKEN_RE = /^[a-z0-9-]+$/;
19401
+ var NO_FLAGS = new Set;
19399
19402
  function isValidClientFlagToken(token) {
19400
19403
  return CLIENT_FLAG_TOKEN_RE.test(token);
19401
19404
  }
@@ -42600,6 +42603,257 @@ var SYSTEM_INFRA_SHAPE_NAMES = new Set(SYSTEM_COMPONENTS.filter((entry) => entry
42600
42603
  function findSystemComponent(componentId) {
42601
42604
  return SYSTEM_COMPONENTS.find((entry) => entry.componentId === componentId);
42602
42605
  }
42606
+ // ../../packages/sdk-ts/src/collection-operation-normalize.ts
42607
+ var collectionTypes2 = new Set([
42608
+ "arc",
42609
+ "bond",
42610
+ "pair",
42611
+ "set",
42612
+ "list"
42613
+ ]);
42614
+ function normalizeBackendCollectionAdd(operation, source) {
42615
+ const normalized = normalizeCollectionWrite(operation, source, "add");
42616
+ return {
42617
+ operation: "add",
42618
+ kind: "collection",
42619
+ name: normalized.name,
42620
+ type: normalized.type,
42621
+ members: normalized.members,
42622
+ ...operation.skipExisting === true ? { skipExisting: true } : {}
42623
+ };
42624
+ }
42625
+ function normalizeBackendCollectionRevise(operation, source) {
42626
+ const normalized = normalizeCollectionWrite(operation, source, "revise");
42627
+ if (!normalized.name) {
42628
+ throw new Error(`${source}: collection revise requires a target name`);
42629
+ }
42630
+ return {
42631
+ operation: "revise",
42632
+ kind: "collection",
42633
+ name: normalized.name,
42634
+ type: normalized.type,
42635
+ members: normalized.members,
42636
+ ...typeof operation.expectedVersion === "number" ? { expectedVersion: operation.expectedVersion } : {},
42637
+ ...typeof operation.leaseId === "string" && operation.leaseId.length > 0 ? { leaseId: operation.leaseId } : {}
42638
+ };
42639
+ }
42640
+ function normalizeCollectionWrite(operation, source, writeOperation) {
42641
+ const diagnostics = preflightOpDiagnostics2({
42642
+ operation: writeOperation,
42643
+ kind: "collection",
42644
+ name: typeof operation.name === "string" ? operation.name : undefined,
42645
+ type: typeof operation.type === "string" ? operation.type : undefined,
42646
+ members: Array.isArray(operation.members) ? operation.members : undefined
42647
+ }, 0);
42648
+ if (diagnostics.length > 0) {
42649
+ throw new Error(`${source}: ${diagnostics.map((diagnostic) => diagnostic.message).join("; ")}`);
42650
+ }
42651
+ const type = normalizeCollectionType(operation.type);
42652
+ if (!type) {
42653
+ throw new Error(`${source}: collection ${writeOperation} requires 'type'. Use one of: arc, bond, set, list`);
42654
+ }
42655
+ if (!Array.isArray(operation.members)) {
42656
+ throw new Error(`${source}: collection ${writeOperation} requires a 'members' array`);
42657
+ }
42658
+ const name = normalizeOptionalName(operation.name);
42659
+ if (!name) {
42660
+ throw new Error(`${source}: collection ${writeOperation} name must be a non-empty string`);
42661
+ }
42662
+ assertNoUnsupportedCollectionFields(operation, source, writeOperation);
42663
+ return {
42664
+ name,
42665
+ type,
42666
+ members: operation.members
42667
+ };
42668
+ }
42669
+ function assertNoUnsupportedCollectionFields(operation, source, writeOperation) {
42670
+ const unsupportedFields = [
42671
+ "about",
42672
+ "aboutWref",
42673
+ "shapeWref",
42674
+ "data",
42675
+ ...writeOperation === "revise" ? ["skipExisting"] : []
42676
+ ].filter((field) => operation[field] !== undefined);
42677
+ if (unsupportedFields.length > 0) {
42678
+ throw new Error(`${source}: collection ${writeOperation} does not support ${unsupportedFields.map((field) => `'${field}'`).join(", ")}`);
42679
+ }
42680
+ }
42681
+ function normalizeCollectionType(value) {
42682
+ if (typeof value !== "string")
42683
+ return;
42684
+ const trimmed = value.trim();
42685
+ if (!collectionTypes2.has(trimmed))
42686
+ return;
42687
+ return trimmed;
42688
+ }
42689
+ function normalizeOptionalName(value) {
42690
+ if (typeof value !== "string")
42691
+ return;
42692
+ if (value.length === 0 || value.trim() !== value)
42693
+ return;
42694
+ return value;
42695
+ }
42696
+
42697
+ // ../../packages/sdk-ts/src/operation-normalize.ts
42698
+ function toBackendStreamOperation(operation) {
42699
+ if (operation.expectedVersion !== undefined && operation.operation !== "revise" && operation.operation !== "retract" && operation.operation !== "reaffirm") {
42700
+ throw new Error("expectedVersion is only valid on revise, retract, or reaffirm operations — set an explicit operation discriminator");
42701
+ }
42702
+ if (operation.operation === "retract") {
42703
+ return {
42704
+ operation: "retract",
42705
+ name: operation.name,
42706
+ ...operation.kind ? { kind: operation.kind } : {},
42707
+ ...operation.reason ? { reason: operation.reason } : {},
42708
+ ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
42709
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
42710
+ };
42711
+ }
42712
+ if (operation.operation === "reaffirm") {
42713
+ return {
42714
+ operation: "reaffirm",
42715
+ name: operation.name,
42716
+ ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
42717
+ ...operation.kind ? { kind: operation.kind } : {},
42718
+ ...operation.add ? { add: operation.add } : {},
42719
+ ...operation.remove ? { remove: operation.remove } : {},
42720
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
42721
+ };
42722
+ }
42723
+ if (operation.operation === "rename") {
42724
+ return {
42725
+ operation: "rename",
42726
+ name: operation.name,
42727
+ newName: operation.newName,
42728
+ ...operation.kind ? { kind: operation.kind } : {}
42729
+ };
42730
+ }
42731
+ if (operation.operation === "revise") {
42732
+ const name = operation.name ?? operation.wref;
42733
+ if (!name) {
42734
+ throw new Error("revise operation requires a target");
42735
+ }
42736
+ const kind2 = inferOperationKind({ kind: operation.kind, name });
42737
+ if (Object.hasOwn(operation, "active")) {
42738
+ throw new Error(`${kind2} revise operation no longer supports 'active' — use retract('${name}') instead`);
42739
+ }
42740
+ if (kind2 !== "assertion" && operation.affirmedTargets !== undefined) {
42741
+ throw new Error(`${kind2} revise operation does not support 'affirmedTargets' — it applies only to assertions; set kind: 'assertion' explicitly`);
42742
+ }
42743
+ if (kind2 === "collection") {
42744
+ return normalizeBackendCollectionRevise(operation, "commit.apply");
42745
+ }
42746
+ if (operation.data === undefined) {
42747
+ throw new Error(`${kind2} revise operation requires 'data'`);
42748
+ }
42749
+ if (kind2 === "assertion") {
42750
+ return {
42751
+ operation: "revise",
42752
+ kind: "assertion",
42753
+ name,
42754
+ data: operation.data,
42755
+ ...operation.affirmedTargets ? { affirmedTargets: operation.affirmedTargets } : {},
42756
+ ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
42757
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
42758
+ };
42759
+ }
42760
+ return {
42761
+ operation: "revise",
42762
+ kind: kind2,
42763
+ name,
42764
+ data: operation.data,
42765
+ ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
42766
+ ...operation.leaseId ? { leaseId: operation.leaseId } : {}
42767
+ };
42768
+ }
42769
+ const kind = inferOperationKind(operation);
42770
+ const skipExisting = "skipExisting" in operation ? operation.skipExisting : undefined;
42771
+ if (kind !== "collection" && (("type" in operation) && operation.type !== undefined || ("members" in operation) && operation.members !== undefined)) {
42772
+ throw new Error(`add operation '${operation.name ?? ""}' has collection fields but resolved kind '${kind}' — collection adds require both 'type' and 'members', or set kind: 'collection'`);
42773
+ }
42774
+ if (kind !== "assertion" && operation.affirmedTargets !== undefined) {
42775
+ throw new Error(`${kind} add operation does not support 'affirmedTargets' — it applies only to assertions; set kind: 'assertion' explicitly`);
42776
+ }
42777
+ if (kind === "collection") {
42778
+ return normalizeBackendCollectionAdd({ ...operation, skipExisting }, "commit.apply");
42779
+ }
42780
+ if (!operation.name) {
42781
+ throw new Error("add operation requires a name");
42782
+ }
42783
+ if (kind === "assertion") {
42784
+ if (!("about" in operation) || operation.about === undefined) {
42785
+ throw new Error("assertion add operation requires 'about'");
42786
+ }
42787
+ if (typeof operation.about !== "string") {
42788
+ throw new Error(COLLECTION_ABOUT_REMOVED_MESSAGE);
42789
+ }
42790
+ if (operation.data === undefined) {
42791
+ throw new Error("assertion add operation requires 'data'");
42792
+ }
42793
+ return {
42794
+ operation: "add",
42795
+ kind: "assertion",
42796
+ name: operation.name,
42797
+ about: operation.about,
42798
+ data: operation.data,
42799
+ ...operation.affirmedTargets ? { affirmedTargets: operation.affirmedTargets } : {},
42800
+ ...skipExisting === true ? { skipExisting } : {}
42801
+ };
42802
+ }
42803
+ if (operation.data === undefined) {
42804
+ throw new Error(`${kind} add operation requires 'data'`);
42805
+ }
42806
+ return {
42807
+ operation: "add",
42808
+ kind,
42809
+ name: operation.name,
42810
+ data: operation.data,
42811
+ ...skipExisting === true ? { skipExisting } : {}
42812
+ };
42813
+ }
42814
+
42815
+ // ../../packages/sdk-ts/src/commit-validation.ts
42816
+ var MAX_COMMIT_VALIDATION_OPERATIONS = 1e4;
42817
+ var MAX_COMMIT_VALIDATION_ENCODED_BYTES = 4 * 1024 * 1024;
42818
+
42819
+ class CommitValidationInputError extends Error {
42820
+ code = "VALIDATION_ERROR";
42821
+ status = 400;
42822
+ constructor(message) {
42823
+ super(message);
42824
+ this.name = "WarmHubError";
42825
+ }
42826
+ }
42827
+ function normalizeCommitValidationOperations(operations, skipExisting) {
42828
+ return operations.map((operation, index) => {
42829
+ let normalized;
42830
+ try {
42831
+ normalized = toBackendStreamOperation(operation);
42832
+ } catch (error51) {
42833
+ const message = error51 instanceof Error ? error51.message : String(error51);
42834
+ throw new CommitValidationInputError(`Invalid operation at index ${index}: ${message}`);
42835
+ }
42836
+ return skipExisting === true && normalized.operation === "add" ? { ...normalized, skipExisting: true } : normalized;
42837
+ });
42838
+ }
42839
+ function createCommitValidateInput(orgName, repoName, operations, opts) {
42840
+ return {
42841
+ orgName,
42842
+ repoName,
42843
+ ...opts?.committer !== undefined ? { committer: opts.committer } : {},
42844
+ ...opts?.message !== undefined ? { message: opts.message } : {},
42845
+ ...opts?.componentRef !== undefined ? { componentRef: opts.componentRef } : {},
42846
+ operations: normalizeCommitValidationOperations(operations, opts?.skipExisting),
42847
+ ...opts?.includeWouldBeBody !== undefined ? { includeWouldBeBody: opts.includeWouldBeBody } : {}
42848
+ };
42849
+ }
42850
+ function encodeCommitValidateRequestBody(input) {
42851
+ return JSON.stringify(input);
42852
+ }
42853
+ function commitValidateRequestBodyBytes(input) {
42854
+ return new TextEncoder().encode(encodeCommitValidateRequestBody(input)).byteLength;
42855
+ }
42856
+
42603
42857
  // ../../packages/sdk-ts/src/grant-client.ts
42604
42858
  function createGrantClient(getTrpc, mapError) {
42605
42859
  return {
@@ -42746,160 +43000,38 @@ function shapeDefinitionPreflightError(name, data, verb) {
42746
43000
  return `Invalid shape definition for "${name}": ${result.errors.join("; ")}`;
42747
43001
  }
42748
43002
 
42749
- // ../../packages/sdk-ts/src/stream-submit-utils.ts
42750
- function streamAppendResultStatus(result) {
42751
- const status = "status" in result ? result.status : undefined;
42752
- if (status === "failed") {
42753
- return "error";
42754
- }
42755
- return status === "noop" || result.operation === "noop" ? "noop" : "applied";
42756
- }
42757
- var DEFAULT_RETRY_POLICY = {
42758
- maxAttempts: 3,
42759
- baseDelayMs: 250,
42760
- maxDelayMs: 8000
42761
- };
42762
- var MAX_ATTEMPTS_HARD_CAP = 10;
42763
- var MAX_DELAY_HARD_CAP_MS = 60000;
42764
- function resolveRetryPolicy(retry) {
42765
- if (retry === false)
42766
- return false;
42767
- const overrides = {};
42768
- if (retry !== undefined) {
42769
- if (Number.isFinite(retry.maxAttempts))
42770
- overrides.maxAttempts = retry.maxAttempts;
42771
- if (Number.isFinite(retry.baseDelayMs))
42772
- overrides.baseDelayMs = retry.baseDelayMs;
42773
- if (Number.isFinite(retry.maxDelayMs))
42774
- overrides.maxDelayMs = retry.maxDelayMs;
42775
- }
42776
- const merged = { ...DEFAULT_RETRY_POLICY, ...overrides };
42777
- return {
42778
- maxAttempts: Math.min(MAX_ATTEMPTS_HARD_CAP, Math.max(1, Math.trunc(merged.maxAttempts))),
42779
- baseDelayMs: Math.min(MAX_DELAY_HARD_CAP_MS, Math.max(0, Math.trunc(merged.baseDelayMs))),
42780
- maxDelayMs: Math.min(MAX_DELAY_HARD_CAP_MS, Math.max(0, Math.trunc(merged.maxDelayMs)))
42781
- };
42782
- }
42783
- var DEFINITE_CLIENT_REJECT_CODES = new Set([
42784
- "BAD_REQUEST",
42785
- "METHOD_NOT_SUPPORTED",
42786
- "PARSE_ERROR",
42787
- "PAYLOAD_TOO_LARGE",
42788
- "PRECONDITION_FAILED",
42789
- "UNAUTHORIZED",
42790
- "UNPROCESSABLE_CONTENT",
42791
- "UNSUPPORTED_MEDIA_TYPE",
42792
- "UNAUTHENTICATED",
42793
- "FORBIDDEN",
42794
- "VALIDATION_ERROR",
42795
- "SHAPE_MISMATCH",
42796
- "RESERVED_NAME",
42797
- "ILLEGAL_OP_SEQUENCE",
42798
- "NOT_FOUND",
42799
- "KIND_MISMATCH",
42800
- "CONFLICT",
42801
- "ALREADY_RETRACTED",
42802
- "ARCHIVED",
42803
- "RATE_LIMITED",
42804
- "TOO_MANY_REQUESTS",
42805
- "UNRESOLVED_TOKEN"
42806
- ]);
42807
- var TERMINAL_AMBIGUOUS_CODES = new Set(["COMMIT_OUTCOME_UNKNOWN"]);
42808
- function extractErrorCode(cause) {
42809
- if (!cause || typeof cause !== "object")
42810
- return;
42811
- const direct = cause.code;
42812
- if (typeof direct === "string")
42813
- return direct;
42814
- const data = cause.data;
42815
- const wh = data?.warmhub?.code;
42816
- if (typeof wh === "string")
42817
- return wh;
42818
- const dc = data?.code;
42819
- if (typeof dc === "string")
42820
- return dc;
42821
- return;
42822
- }
42823
- function extractHttpStatus(cause) {
42824
- if (!cause || typeof cause !== "object")
42825
- return;
42826
- const status = cause.data?.httpStatus;
42827
- if (typeof status === "number")
42828
- return status;
42829
- const warmhubStatus = cause.data?.warmhub?.status;
42830
- if (typeof warmhubStatus === "number")
42831
- return warmhubStatus;
42832
- const direct = cause.status;
42833
- return typeof direct === "number" ? direct : undefined;
42834
- }
42835
- function isDefiniteClientRejectionStatus(status) {
42836
- return status !== undefined && status >= 400 && status < 500 && status !== 408;
42837
- }
42838
- function isTerminalAmbiguousError(cause) {
42839
- const code = extractErrorCode(cause);
42840
- return code !== undefined && TERMINAL_AMBIGUOUS_CODES.has(code);
42841
- }
42842
- function isDefiniteStreamAppendRejection(cause) {
42843
- if (isTerminalAmbiguousError(cause))
42844
- return false;
42845
- const code = extractErrorCode(cause);
42846
- return code !== undefined && DEFINITE_CLIENT_REJECT_CODES.has(code) || isDefiniteClientRejectionStatus(extractHttpStatus(cause));
42847
- }
42848
- function isTransientStreamFailure(cause) {
42849
- if (isTerminalAmbiguousError(cause))
42850
- return false;
42851
- if (isDefiniteStreamAppendRejection(cause))
42852
- return false;
42853
- if (isFetchNetworkTypeError(cause))
42854
- return true;
42855
- if (cause instanceof TypeError)
42856
- return false;
42857
- if (cause instanceof SyntaxError)
42858
- return false;
42859
- const inner = cause?.cause;
42860
- if (isFetchNetworkTypeError(inner))
42861
- return true;
42862
- if (inner instanceof TypeError)
42863
- return false;
42864
- if (inner instanceof SyntaxError)
42865
- return false;
42866
- return true;
42867
- }
42868
- function isFetchNetworkTypeError(cause) {
42869
- return cause instanceof TypeError && /fetch/i.test(cause.message);
42870
- }
42871
- function computeBackoffDelayMs(attempt, policy) {
42872
- const exponential = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);
42873
- const jitter = Math.random() * policy.baseDelayMs;
42874
- return Math.min(policy.maxDelayMs, exponential + jitter);
42875
- }
42876
- function sleep2(ms) {
42877
- return new Promise((resolve) => setTimeout(resolve, ms));
42878
- }
42879
-
42880
43003
  // ../../packages/sdk-ts/src/stream-submit-aggregate.ts
42881
- function toSubmittedOperation(result, opIndex, status, submittedName) {
42882
- const row = {
43004
+ function toSubmittedOperation(result, opIndex, submitted) {
43005
+ const operation = result.operation === undefined || result.operation === "noop" ? submitted?.operation ?? "add" : result.operation;
43006
+ const base = {
42883
43007
  opIndex,
42884
43008
  name: result.name ?? "",
42885
- operation: result.operation === "revise" || result.operation === "retract" || result.operation === "reaffirm" || result.operation === "rename" || result.operation === "noop" ? result.operation : "add",
42886
- dataHash: result.dataHash ?? "",
42887
- version: result.version ?? 0,
42888
- status,
42889
- error: result.error
42890
- };
42891
- if (result.affirmations)
42892
- row.affirmations = result.affirmations;
42893
- if (result.status === "failed" && submittedName !== undefined) {
42894
- row.submittedName = submittedName;
42895
- }
42896
- if (result.resolvedName !== undefined)
42897
- row.resolvedName = result.resolvedName;
42898
- if (result.retryable !== undefined)
42899
- row.retryable = result.retryable;
42900
- if (result.warnings)
42901
- row.warnings = result.warnings;
42902
- return row;
43009
+ operation,
43010
+ ...result.affirmations ? { affirmations: result.affirmations } : {},
43011
+ ...result.resolvedName !== undefined ? { resolvedName: result.resolvedName } : {},
43012
+ ...result.warnings ? { warnings: result.warnings } : {}
43013
+ };
43014
+ if (result.status === "failed") {
43015
+ return {
43016
+ ...base,
43017
+ status: "error",
43018
+ errors: [
43019
+ {
43020
+ code: result.error?.code ?? "BACKEND",
43021
+ message: result.error?.message ?? `Operation on "${result.name}" failed`,
43022
+ ...result.error?.details ? { details: result.error.details } : {},
43023
+ ...result.retryable !== undefined ? { retryable: result.retryable } : {}
43024
+ }
43025
+ ],
43026
+ ...submitted?.name !== undefined ? { submittedName: submitted.name } : {}
43027
+ };
43028
+ }
43029
+ return {
43030
+ ...base,
43031
+ status: result.status === "noop" || result.operation === "noop" ? "noop" : "applied",
43032
+ ...result.version !== undefined ? { version: result.version } : {},
43033
+ ...result.dataHash !== undefined ? { dataHash: result.dataHash } : {}
43034
+ };
42903
43035
  }
42904
43036
 
42905
43037
  class StreamSubmissionAggregator {
@@ -42910,11 +43042,22 @@ class StreamSubmissionAggregator {
42910
43042
  return this.operations.length > 0 && this.statusCounts.error === this.operations.length;
42911
43043
  }
42912
43044
  addChunk(input) {
42913
- input.results.forEach((result, position) => {
43045
+ if (input.result.schemaVersion === "operation-event-receipt/v2") {
43046
+ input.result.results.forEach((result) => {
43047
+ this.statusCounts[result.status]++;
43048
+ this.operations.push({
43049
+ ...result,
43050
+ opIndex: input.chunkStart + result.opIndex
43051
+ });
43052
+ });
43053
+ return;
43054
+ }
43055
+ input.result.results.forEach((result, position) => {
42914
43056
  const local = result.opIndex ?? position;
42915
- const status = streamAppendResultStatus(result);
42916
- this.statusCounts[status]++;
42917
- this.operations.push(toSubmittedOperation(result, input.chunkStart + local, status, input.submittedNames?.[local]?.name));
43057
+ const submitted = input.submittedOperations[local];
43058
+ const operation = toSubmittedOperation(result, input.chunkStart + local, submitted);
43059
+ this.statusCounts[operation.status]++;
43060
+ this.operations.push(operation);
42918
43061
  });
42919
43062
  }
42920
43063
  addReceipt(receipt) {
@@ -43039,215 +43182,6 @@ function operationEventStreamRequestId(submissionId, chunkOrdinal) {
43039
43182
  return formatUuid(digest);
43040
43183
  }
43041
43184
 
43042
- // ../../packages/sdk-ts/src/collection-operation-normalize.ts
43043
- var collectionTypes2 = new Set([
43044
- "arc",
43045
- "bond",
43046
- "pair",
43047
- "set",
43048
- "list"
43049
- ]);
43050
- function normalizeBackendCollectionAdd(operation, source) {
43051
- const normalized = normalizeCollectionWrite(operation, source, "add");
43052
- return {
43053
- operation: "add",
43054
- kind: "collection",
43055
- name: normalized.name,
43056
- type: normalized.type,
43057
- members: normalized.members,
43058
- ...operation.skipExisting === true ? { skipExisting: true } : {}
43059
- };
43060
- }
43061
- function normalizeBackendCollectionRevise(operation, source) {
43062
- const normalized = normalizeCollectionWrite(operation, source, "revise");
43063
- if (!normalized.name) {
43064
- throw new Error(`${source}: collection revise requires a target name`);
43065
- }
43066
- return {
43067
- operation: "revise",
43068
- kind: "collection",
43069
- name: normalized.name,
43070
- type: normalized.type,
43071
- members: normalized.members,
43072
- ...typeof operation.expectedVersion === "number" ? { expectedVersion: operation.expectedVersion } : {},
43073
- ...typeof operation.leaseId === "string" && operation.leaseId.length > 0 ? { leaseId: operation.leaseId } : {}
43074
- };
43075
- }
43076
- function normalizeCollectionWrite(operation, source, writeOperation) {
43077
- const diagnostics = preflightOpDiagnostics2({
43078
- operation: writeOperation,
43079
- kind: "collection",
43080
- name: typeof operation.name === "string" ? operation.name : undefined,
43081
- type: typeof operation.type === "string" ? operation.type : undefined,
43082
- members: Array.isArray(operation.members) ? operation.members : undefined
43083
- }, 0);
43084
- if (diagnostics.length > 0) {
43085
- throw new Error(`${source}: ${diagnostics.map((diagnostic) => diagnostic.message).join("; ")}`);
43086
- }
43087
- const type = normalizeCollectionType(operation.type);
43088
- if (!type) {
43089
- throw new Error(`${source}: collection ${writeOperation} requires 'type'. Use one of: arc, bond, set, list`);
43090
- }
43091
- if (!Array.isArray(operation.members)) {
43092
- throw new Error(`${source}: collection ${writeOperation} requires a 'members' array`);
43093
- }
43094
- const name = normalizeOptionalName(operation.name);
43095
- if (!name) {
43096
- throw new Error(`${source}: collection ${writeOperation} name must be a non-empty string`);
43097
- }
43098
- assertNoUnsupportedCollectionFields(operation, source, writeOperation);
43099
- return {
43100
- name,
43101
- type,
43102
- members: operation.members
43103
- };
43104
- }
43105
- function assertNoUnsupportedCollectionFields(operation, source, writeOperation) {
43106
- const unsupportedFields = [
43107
- "about",
43108
- "aboutWref",
43109
- "shapeWref",
43110
- "data",
43111
- ...writeOperation === "revise" ? ["skipExisting"] : []
43112
- ].filter((field) => operation[field] !== undefined);
43113
- if (unsupportedFields.length > 0) {
43114
- throw new Error(`${source}: collection ${writeOperation} does not support ${unsupportedFields.map((field) => `'${field}'`).join(", ")}`);
43115
- }
43116
- }
43117
- function normalizeCollectionType(value) {
43118
- if (typeof value !== "string")
43119
- return;
43120
- const trimmed = value.trim();
43121
- if (!collectionTypes2.has(trimmed))
43122
- return;
43123
- return trimmed;
43124
- }
43125
- function normalizeOptionalName(value) {
43126
- if (typeof value !== "string")
43127
- return;
43128
- if (value.length === 0 || value.trim() !== value)
43129
- return;
43130
- return value;
43131
- }
43132
-
43133
- // ../../packages/sdk-ts/src/operation-normalize.ts
43134
- function toBackendStreamOperation(operation) {
43135
- if (operation.expectedVersion !== undefined && operation.operation !== "revise" && operation.operation !== "retract" && operation.operation !== "reaffirm") {
43136
- throw new Error("expectedVersion is only valid on revise, retract, or reaffirm operations — set an explicit operation discriminator");
43137
- }
43138
- if (operation.operation === "retract") {
43139
- return {
43140
- operation: "retract",
43141
- name: operation.name,
43142
- ...operation.kind ? { kind: operation.kind } : {},
43143
- ...operation.reason ? { reason: operation.reason } : {},
43144
- ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
43145
- ...operation.leaseId ? { leaseId: operation.leaseId } : {}
43146
- };
43147
- }
43148
- if (operation.operation === "reaffirm") {
43149
- return {
43150
- operation: "reaffirm",
43151
- name: operation.name,
43152
- ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
43153
- ...operation.kind ? { kind: operation.kind } : {},
43154
- ...operation.add ? { add: operation.add } : {},
43155
- ...operation.remove ? { remove: operation.remove } : {},
43156
- ...operation.leaseId ? { leaseId: operation.leaseId } : {}
43157
- };
43158
- }
43159
- if (operation.operation === "rename") {
43160
- return {
43161
- operation: "rename",
43162
- name: operation.name,
43163
- newName: operation.newName,
43164
- ...operation.kind ? { kind: operation.kind } : {}
43165
- };
43166
- }
43167
- if (operation.operation === "revise") {
43168
- const name = operation.name ?? operation.wref;
43169
- if (!name) {
43170
- throw new Error("revise operation requires a target");
43171
- }
43172
- const kind2 = inferOperationKind({ kind: operation.kind, name });
43173
- if (Object.hasOwn(operation, "active")) {
43174
- throw new Error(`${kind2} revise operation no longer supports 'active' — use retract('${name}') instead`);
43175
- }
43176
- if (kind2 !== "assertion" && operation.affirmedTargets !== undefined) {
43177
- throw new Error(`${kind2} revise operation does not support 'affirmedTargets' — it applies only to assertions; set kind: 'assertion' explicitly`);
43178
- }
43179
- if (kind2 === "collection") {
43180
- return normalizeBackendCollectionRevise(operation, "commit.apply");
43181
- }
43182
- if (operation.data === undefined) {
43183
- throw new Error(`${kind2} revise operation requires 'data'`);
43184
- }
43185
- if (kind2 === "assertion") {
43186
- return {
43187
- operation: "revise",
43188
- kind: "assertion",
43189
- name,
43190
- data: operation.data,
43191
- ...operation.affirmedTargets ? { affirmedTargets: operation.affirmedTargets } : {},
43192
- ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
43193
- ...operation.leaseId ? { leaseId: operation.leaseId } : {}
43194
- };
43195
- }
43196
- return {
43197
- operation: "revise",
43198
- kind: kind2,
43199
- name,
43200
- data: operation.data,
43201
- ...operation.expectedVersion !== undefined ? { expectedVersion: operation.expectedVersion } : {},
43202
- ...operation.leaseId ? { leaseId: operation.leaseId } : {}
43203
- };
43204
- }
43205
- const kind = inferOperationKind(operation);
43206
- const skipExisting = "skipExisting" in operation ? operation.skipExisting : undefined;
43207
- if (kind !== "collection" && (("type" in operation) && operation.type !== undefined || ("members" in operation) && operation.members !== undefined)) {
43208
- throw new Error(`add operation '${operation.name ?? ""}' has collection fields but resolved kind '${kind}' — collection adds require both 'type' and 'members', or set kind: 'collection'`);
43209
- }
43210
- if (kind !== "assertion" && operation.affirmedTargets !== undefined) {
43211
- throw new Error(`${kind} add operation does not support 'affirmedTargets' — it applies only to assertions; set kind: 'assertion' explicitly`);
43212
- }
43213
- if (kind === "collection") {
43214
- return normalizeBackendCollectionAdd({ ...operation, skipExisting }, "commit.apply");
43215
- }
43216
- if (!operation.name) {
43217
- throw new Error("add operation requires a name");
43218
- }
43219
- if (kind === "assertion") {
43220
- if (!("about" in operation) || operation.about === undefined) {
43221
- throw new Error("assertion add operation requires 'about'");
43222
- }
43223
- if (typeof operation.about !== "string") {
43224
- throw new Error(COLLECTION_ABOUT_REMOVED_MESSAGE);
43225
- }
43226
- if (operation.data === undefined) {
43227
- throw new Error("assertion add operation requires 'data'");
43228
- }
43229
- return {
43230
- operation: "add",
43231
- kind: "assertion",
43232
- name: operation.name,
43233
- about: operation.about,
43234
- data: operation.data,
43235
- ...operation.affirmedTargets ? { affirmedTargets: operation.affirmedTargets } : {},
43236
- ...skipExisting === true ? { skipExisting } : {}
43237
- };
43238
- }
43239
- if (operation.data === undefined) {
43240
- throw new Error(`${kind} add operation requires 'data'`);
43241
- }
43242
- return {
43243
- operation: "add",
43244
- kind,
43245
- name: operation.name,
43246
- data: operation.data,
43247
- ...skipExisting === true ? { skipExisting } : {}
43248
- };
43249
- }
43250
-
43251
43185
  // ../../packages/sdk-ts/src/stream-submit-types.ts
43252
43186
  var DEFAULT_STREAM_CHUNK_SIZE = DEFAULT_STREAM_APPEND_CHUNK_SIZE;
43253
43187
  var MAX_STREAM_APPEND_OPERATION_COUNT2 = MAX_STREAM_APPEND_OPERATION_COUNT;
@@ -43290,7 +43224,7 @@ class AllStreamOperationsFailedError extends Error {
43290
43224
  statusCounts;
43291
43225
  cause;
43292
43226
  constructor(result) {
43293
- const primaryFailure = result.operations.find((operation) => operation.error !== undefined)?.error;
43227
+ const primaryFailure = result.operations.find((operation) => operation.status === "error")?.errors[0];
43294
43228
  super(primaryFailure ? `All ${result.operationCount} stream operations failed: ${primaryFailure.message}` : `All ${result.operationCount} stream operations failed.`);
43295
43229
  this.name = "WarmHubError";
43296
43230
  this.cause = primaryFailure;
@@ -43305,6 +43239,130 @@ class AllStreamOperationsFailedError extends Error {
43305
43239
  }
43306
43240
  }
43307
43241
 
43242
+ // ../../packages/sdk-ts/src/stream-submit-utils.ts
43243
+ var DEFAULT_RETRY_POLICY = {
43244
+ maxAttempts: 3,
43245
+ baseDelayMs: 250,
43246
+ maxDelayMs: 8000
43247
+ };
43248
+ var MAX_ATTEMPTS_HARD_CAP = 10;
43249
+ var MAX_DELAY_HARD_CAP_MS = 60000;
43250
+ function resolveRetryPolicy(retry) {
43251
+ if (retry === false)
43252
+ return false;
43253
+ const overrides = {};
43254
+ if (retry !== undefined) {
43255
+ if (Number.isFinite(retry.maxAttempts))
43256
+ overrides.maxAttempts = retry.maxAttempts;
43257
+ if (Number.isFinite(retry.baseDelayMs))
43258
+ overrides.baseDelayMs = retry.baseDelayMs;
43259
+ if (Number.isFinite(retry.maxDelayMs))
43260
+ overrides.maxDelayMs = retry.maxDelayMs;
43261
+ }
43262
+ const merged = { ...DEFAULT_RETRY_POLICY, ...overrides };
43263
+ return {
43264
+ maxAttempts: Math.min(MAX_ATTEMPTS_HARD_CAP, Math.max(1, Math.trunc(merged.maxAttempts))),
43265
+ baseDelayMs: Math.min(MAX_DELAY_HARD_CAP_MS, Math.max(0, Math.trunc(merged.baseDelayMs))),
43266
+ maxDelayMs: Math.min(MAX_DELAY_HARD_CAP_MS, Math.max(0, Math.trunc(merged.maxDelayMs)))
43267
+ };
43268
+ }
43269
+ var DEFINITE_CLIENT_REJECT_CODES = new Set([
43270
+ "BAD_REQUEST",
43271
+ "METHOD_NOT_SUPPORTED",
43272
+ "PARSE_ERROR",
43273
+ "PAYLOAD_TOO_LARGE",
43274
+ "PRECONDITION_FAILED",
43275
+ "UNAUTHORIZED",
43276
+ "UNPROCESSABLE_CONTENT",
43277
+ "UNSUPPORTED_MEDIA_TYPE",
43278
+ "UNAUTHENTICATED",
43279
+ "FORBIDDEN",
43280
+ "VALIDATION_ERROR",
43281
+ "SHAPE_MISMATCH",
43282
+ "RESERVED_NAME",
43283
+ "ILLEGAL_OP_SEQUENCE",
43284
+ "NOT_FOUND",
43285
+ "KIND_MISMATCH",
43286
+ "CONFLICT",
43287
+ "ALREADY_RETRACTED",
43288
+ "ARCHIVED",
43289
+ "RATE_LIMITED",
43290
+ "TOO_MANY_REQUESTS",
43291
+ "UNRESOLVED_TOKEN"
43292
+ ]);
43293
+ var TERMINAL_AMBIGUOUS_CODES = new Set(["COMMIT_OUTCOME_UNKNOWN"]);
43294
+ function extractErrorCode(cause) {
43295
+ if (!cause || typeof cause !== "object")
43296
+ return;
43297
+ const direct = cause.code;
43298
+ if (typeof direct === "string")
43299
+ return direct;
43300
+ const data = cause.data;
43301
+ const wh = data?.warmhub?.code;
43302
+ if (typeof wh === "string")
43303
+ return wh;
43304
+ const dc = data?.code;
43305
+ if (typeof dc === "string")
43306
+ return dc;
43307
+ return;
43308
+ }
43309
+ function extractHttpStatus(cause) {
43310
+ if (!cause || typeof cause !== "object")
43311
+ return;
43312
+ const status = cause.data?.httpStatus;
43313
+ if (typeof status === "number")
43314
+ return status;
43315
+ const warmhubStatus = cause.data?.warmhub?.status;
43316
+ if (typeof warmhubStatus === "number")
43317
+ return warmhubStatus;
43318
+ const direct = cause.status;
43319
+ return typeof direct === "number" ? direct : undefined;
43320
+ }
43321
+ function isDefiniteClientRejectionStatus(status) {
43322
+ return status !== undefined && status >= 400 && status < 500 && status !== 408;
43323
+ }
43324
+ function isTerminalAmbiguousError(cause) {
43325
+ const code = extractErrorCode(cause);
43326
+ return code !== undefined && TERMINAL_AMBIGUOUS_CODES.has(code);
43327
+ }
43328
+ function isDefiniteStreamAppendRejection(cause) {
43329
+ if (isTerminalAmbiguousError(cause))
43330
+ return false;
43331
+ const code = extractErrorCode(cause);
43332
+ return code !== undefined && DEFINITE_CLIENT_REJECT_CODES.has(code) || isDefiniteClientRejectionStatus(extractHttpStatus(cause));
43333
+ }
43334
+ function isTransientStreamFailure(cause) {
43335
+ if (isTerminalAmbiguousError(cause))
43336
+ return false;
43337
+ if (isDefiniteStreamAppendRejection(cause))
43338
+ return false;
43339
+ if (isFetchNetworkTypeError(cause))
43340
+ return true;
43341
+ if (cause instanceof TypeError)
43342
+ return false;
43343
+ if (cause instanceof SyntaxError)
43344
+ return false;
43345
+ const inner = cause?.cause;
43346
+ if (isFetchNetworkTypeError(inner))
43347
+ return true;
43348
+ if (inner instanceof TypeError)
43349
+ return false;
43350
+ if (inner instanceof SyntaxError)
43351
+ return false;
43352
+ return true;
43353
+ }
43354
+ function isFetchNetworkTypeError(cause) {
43355
+ return cause instanceof TypeError && /fetch/i.test(cause.message);
43356
+ }
43357
+ function computeBackoffDelayMs(attempt, policy) {
43358
+ const exponential = policy.baseDelayMs * 2 ** Math.max(0, attempt - 1);
43359
+ const jitter = Math.random() * policy.baseDelayMs;
43360
+ return Math.min(policy.maxDelayMs, exponential + jitter);
43361
+ }
43362
+ function sleep2(ms) {
43363
+ return new Promise((resolve) => setTimeout(resolve, ms));
43364
+ }
43365
+
43308
43366
  // ../../packages/sdk-ts/src/stream-submit-submit.ts
43309
43367
  class StreamValidationError extends Error {
43310
43368
  code;
@@ -43371,9 +43429,9 @@ async function submitOperationsViaStream(client, args) {
43371
43429
  createdByEmail = appendResult.createdByEmail ?? createdByEmail;
43372
43430
  repoSeq = appendResult.repoSeq ?? repoSeq;
43373
43431
  aggregate.addChunk({
43374
- results: appendResult.results,
43432
+ result: appendResult,
43375
43433
  chunkStart: chunk.start,
43376
- submittedNames: chunk.operations
43434
+ submittedOperations: chunk.operations
43377
43435
  });
43378
43436
  acknowledgedOperationCount += chunk.operations.length;
43379
43437
  const acknowledgedRepoSeq = receiptRepoSeq(appendResult.receipt);
@@ -43489,7 +43547,7 @@ function normalizeChunkSize(chunkSize) {
43489
43547
  // ../../packages/sdk-ts/package.json
43490
43548
  var package_default = {
43491
43549
  name: "@warmhub/sdk-ts",
43492
- version: "0.91.0",
43550
+ version: "0.93.0",
43493
43551
  private: false,
43494
43552
  type: "module",
43495
43553
  description: "The TypeScript SDK for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",
@@ -43698,6 +43756,7 @@ function normalizeClientFlags(flags) {
43698
43756
  }
43699
43757
  var DEFAULT_API_URL = "https://api.warmhub.ai";
43700
43758
  var UNBATCHED_TRPC_PATHS = new Set([
43759
+ "commit.validate",
43701
43760
  "repo.shapeInstanceCounts",
43702
43761
  "thing.getMany",
43703
43762
  "thing.headVersions"
@@ -44062,8 +44121,9 @@ class WarmHubClient {
44062
44121
  }
44063
44122
  createCompatibilityLink() {
44064
44123
  return () => ({ op, next }) => {
44065
- if (op.type !== "mutation")
44124
+ if (op.type !== "mutation" || op.path === "commit.validate") {
44066
44125
  return next(op);
44126
+ }
44067
44127
  return observable((observer) => {
44068
44128
  let cancelled = false;
44069
44129
  let downstream;
@@ -44333,6 +44393,21 @@ class WarmHubClient {
44333
44393
  }
44334
44394
  };
44335
44395
  commit = {
44396
+ validate: async (orgName, repoName, operations, opts) => {
44397
+ try {
44398
+ return await this.trpc.commit.validate.mutate({
44399
+ orgName,
44400
+ repoName,
44401
+ committer: opts?.committer,
44402
+ message: opts?.message,
44403
+ componentRef: opts?.componentRef,
44404
+ operations: normalizeCommitValidationOperations(operations, opts?.skipExisting),
44405
+ includeWouldBeBody: opts?.includeWouldBeBody
44406
+ }, { signal: opts?.signal });
44407
+ } catch (error51) {
44408
+ throw toWarmHubError(error51);
44409
+ }
44410
+ },
44336
44411
  apply: async (orgName, repoName, message, operations, opts) => {
44337
44412
  try {
44338
44413
  return await submitOperationsViaStream(this, {
@@ -46334,7 +46409,8 @@ var TOP_LEVEL_USER_INPUT_CODES = new Set([
46334
46409
  "ILLEGAL_OP_SEQUENCE",
46335
46410
  "CURSOR_EPOCH_INVALID",
46336
46411
  "BUILTIN_SHAPE",
46337
- "KIND_MISMATCH"
46412
+ "KIND_MISMATCH",
46413
+ "DEPENDENCY_FAILED"
46338
46414
  ]);
46339
46415
  var CONFLICT_SHAPED_CODES = new Set([
46340
46416
  "CONFLICT",
@@ -46755,6 +46831,22 @@ function toCliError(err) {
46755
46831
  const message = err instanceof Error ? err.message : String(err);
46756
46832
  return new CliError(1 /* Runtime */, "UNKNOWN", message, err);
46757
46833
  }
46834
+ function containedDiagnosticError(diagnostics) {
46835
+ let best;
46836
+ let bestPriority = -1;
46837
+ for (const diagnostic of diagnostics) {
46838
+ const candidate = cliErrorFromOpFailure({
46839
+ status: "error",
46840
+ errors: [diagnostic]
46841
+ });
46842
+ const priority = candidate.code === 5 /* Auth */ ? 3 : candidate.code === 2 /* UserInput */ ? 2 : 1;
46843
+ if (priority > bestPriority) {
46844
+ best = candidate;
46845
+ bestPriority = priority;
46846
+ }
46847
+ }
46848
+ return best;
46849
+ }
46758
46850
  function isFailedOpStatus(status) {
46759
46851
  return status === "failed" || status === "error" || status === "rejected";
46760
46852
  }
@@ -46768,12 +46860,14 @@ var OP_USER_INPUT_CODES = new Set([
46768
46860
  "ILLEGAL_OP_SEQUENCE",
46769
46861
  "CURSOR_EPOCH_INVALID",
46770
46862
  "KIND_MISMATCH",
46771
- "BUILTIN_SHAPE"
46863
+ "BUILTIN_SHAPE",
46864
+ "DEPENDENCY_FAILED"
46772
46865
  ]);
46773
46866
  function cliErrorFromOpFailure(failure) {
46774
- const code = failure.error?.code ?? "BACKEND";
46775
- const message = failure.error?.message ?? `Operation on "${failure.name}" failed`;
46776
- const errorCode = failure.error?.code;
46867
+ const diagnostic = failure.errors?.[0] ?? failure.error;
46868
+ const code = diagnostic?.code ?? "BACKEND";
46869
+ const message = diagnostic?.message ?? `Operation on "${failure.name}" failed`;
46870
+ const errorCode = diagnostic?.code;
46777
46871
  const make = (exit, kind, hint) => new CliError(exit, kind, message, undefined, hint, undefined, errorCode);
46778
46872
  const authError = authCliError({
46779
46873
  code,
@@ -48967,7 +49061,7 @@ var FLAG_CATALOG = [
48967
49061
  spec: {
48968
49062
  long: "dry-run",
48969
49063
  type: "boolean",
48970
- description: "Resolve dispatch and emit JSON envelope; do not call backend"
49064
+ description: "Emit a dispatch plan; commit submit instead runs server validation"
48971
49065
  }
48972
49066
  },
48973
49067
  {
@@ -49198,7 +49292,8 @@ class DomainRegistry {
49198
49292
  args: def.args,
49199
49293
  flags: this.flagsToSpecs(def.flags ?? {}),
49200
49294
  examples: def.examples,
49201
- notes: def.notes
49295
+ notes: def.notes,
49296
+ dryRunBehavior: def.dryRunBehavior ?? "dispatch"
49202
49297
  };
49203
49298
  if (def.globalFlagOverrides) {
49204
49299
  Object.defineProperty(spec, "globalFlagOverrides", {
@@ -49219,7 +49314,8 @@ class DomainRegistry {
49219
49314
  examples: v.examples,
49220
49315
  notes: v.notes,
49221
49316
  verbAliases: v.verbAliases,
49222
- passthroughFlags: v.passthroughFlags
49317
+ passthroughFlags: v.passthroughFlags,
49318
+ dryRunBehavior: v.dryRunBehavior ?? "dispatch"
49223
49319
  };
49224
49320
  if (v.rejectedFlags) {
49225
49321
  Object.defineProperty(verbSpec, "rejectedFlags", {
@@ -50243,7 +50339,7 @@ function emitPartialPageHint(ctx, count, _nextCursor, _limit) {
50243
50339
  }
50244
50340
 
50245
50341
  // ../../packages/warmhub-cli/src/domains/commit-output-contract-id.ts
50246
- var COMMIT_SUBMIT_OUTPUT_SCHEMA_ID = "wh.commit.submit.result/v0.1";
50342
+ var COMMIT_SUBMIT_OUTPUT_SCHEMA_ID = "wh.commit.submit.result/v0.2";
50247
50343
  function identifyCommitSubmitOutput(value) {
50248
50344
  if ("schema" in value) {
50249
50345
  if (value.schema === COMMIT_SUBMIT_OUTPUT_SCHEMA_ID) {
@@ -54216,7 +54312,7 @@ function renderMutation(ctx, result) {
54216
54312
  if (!("version" in operation) || typeof operation.version !== "number") {
54217
54313
  throw new Error("Collection mutation returned no version-bearing operation");
54218
54314
  }
54219
- const isNoop = operation.operation === "noop";
54315
+ const isNoop = operation.status === "noop" || operation.operation === "noop";
54220
54316
  renderSingleOpSuccess(ctx.out, ctx.colors, ctx.chars, operation, {
54221
54317
  marker: isNoop ? "=" : "+",
54222
54318
  color: isNoop ? ctx.colors.dim : ctx.colors.green,
@@ -54932,26 +55028,40 @@ function renderPrettyReceipt(ctx, receipt, committer) {
54932
55028
  const record2 = operation;
54933
55029
  const operationKind = String(record2.operation ?? "operation");
54934
55030
  const failed = isFailedOpStatus(record2.status);
54935
- const marker = failed ? "!" : operationKind === "add" ? "+" : operationKind === "revise" ? "~" : operationKind === "reaffirm" ? "±" : "-";
54936
- const error51 = typeof record2.error === "object" && record2.error !== null ? record2.error : undefined;
55031
+ const marker = failed ? "!" : record2.status === "noop" ? "-" : operationKind === "add" ? "+" : operationKind === "revise" ? "~" : operationKind === "reaffirm" ? "±" : "-";
55032
+ const errors3 = Array.isArray(record2.errors) ? record2.errors : undefined;
55033
+ const error51 = errors3?.[0] ?? (typeof record2.error === "object" && record2.error !== null ? record2.error : undefined);
54937
55034
  const errorSummary = failed ? ` ${error51?.message ?? error51?.code ?? "failed"}` : "";
54938
55035
  ctx.out(` ${marker} ${displayName(c, String(record2.name ?? record2.resolvedName ?? "(unnamed)"))}${errorSummary}`);
54939
55036
  renderWarningLine(ctx.out, c, ctx.chars, operation);
54940
55037
  }
54941
55038
  }
54942
55039
  function allReceiptOperationsFailed(receipts) {
54943
- const operations = receipts.flatMap((receipt) => receipt.operations);
54944
- return operations.length > 0 && operations.every((operation) => isFailedOpStatus(operation.status));
55040
+ let operationCount = 0;
55041
+ for (const receipt of receipts) {
55042
+ for (const operation of receipt.operations) {
55043
+ operationCount++;
55044
+ if (!isFailedOpStatus(operation.status)) {
55045
+ return false;
55046
+ }
55047
+ }
55048
+ }
55049
+ return operationCount > 0;
54945
55050
  }
54946
55051
  function allFailedReceiptError(receipts) {
54947
- return cliErrorFromAllFailed(receipts.flatMap((receipt) => receipt.operations.map((operation) => {
54948
- const record2 = operation;
54949
- return {
54950
- name: typeof record2.name === "string" ? record2.name : undefined,
54951
- status: typeof record2.status === "string" ? record2.status : undefined,
54952
- error: typeof record2.error === "object" && record2.error !== null ? record2.error : undefined
54953
- };
54954
- })));
55052
+ const failures = [];
55053
+ for (const receipt of receipts) {
55054
+ for (const operation of receipt.operations) {
55055
+ const record2 = operation;
55056
+ failures.push({
55057
+ name: typeof record2.name === "string" ? record2.name : undefined,
55058
+ status: typeof record2.status === "string" ? record2.status : undefined,
55059
+ error: typeof record2.error === "object" && record2.error !== null ? record2.error : undefined,
55060
+ errors: Array.isArray(record2.errors) ? record2.errors : undefined
55061
+ });
55062
+ }
55063
+ }
55064
+ return cliErrorFromAllFailed(failures);
54955
55065
  }
54956
55066
  var handleReceiptLookup = async (ctx, { args }) => {
54957
55067
  const eventRequestId = args[0];
@@ -54992,6 +55102,9 @@ var createFlags3 = {
54992
55102
  "return-repo-seq": flag.boolean({
54993
55103
  description: "return the sequence allocated to this caller's own successful write; omitted for noops and all-failed writes"
54994
55104
  }),
55105
+ "include-would-be-body": flag.boolean({
55106
+ description: "Include projected resulting bodies in dry-run output when disclosure rules permit."
55107
+ }),
54995
55108
  "chunk-size": flag.number({
54996
55109
  description: `Streamed ops per append chunk for --stream or .jsonl --file (default: ${DEFAULT_STREAM_APPEND_CHUNK_SIZE}, max: ${MAX_STREAM_APPEND_OPERATION_COUNT})`
54997
55110
  }),
@@ -55059,65 +55172,6 @@ var createFlags3 = {
55059
55172
  })
55060
55173
  };
55061
55174
 
55062
- // ../../packages/warmhub-cli/src/domains/commit-submit-handler.ts
55063
- import { readFile as readFile2 } from "node:fs/promises";
55064
-
55065
- // ../../packages/warmhub-cli/src/commit-payload-validate.ts
55066
- var NUL = String.fromCharCode(0);
55067
- function findInvalidControlByte(data, rootLabel = "data") {
55068
- const segs = [];
55069
- const pos = visit(data, segs);
55070
- return pos === -1 ? null : { path: buildPath(rootLabel, segs), position: pos };
55071
- }
55072
- function visit(value, segs) {
55073
- if (typeof value === "string")
55074
- return value.indexOf(NUL);
55075
- if (value === null || typeof value !== "object")
55076
- return -1;
55077
- if (Array.isArray(value)) {
55078
- for (let i = 0;i < value.length; i++) {
55079
- segs.push(i);
55080
- const p = visit(value[i], segs);
55081
- if (p !== -1)
55082
- return p;
55083
- segs.pop();
55084
- }
55085
- return -1;
55086
- }
55087
- const keys = Object.keys(value);
55088
- for (let i = 0;i < keys.length; i++) {
55089
- const k = keys[i];
55090
- segs.push(k);
55091
- const ki = k.indexOf(NUL);
55092
- if (ki !== -1)
55093
- return ki;
55094
- const p = visit(value[k], segs);
55095
- if (p !== -1)
55096
- return p;
55097
- segs.pop();
55098
- }
55099
- return -1;
55100
- }
55101
- function buildPath(root, segs) {
55102
- let out = root;
55103
- for (const s of segs)
55104
- out += typeof s === "number" ? `[${s}]` : `.${s}`;
55105
- return out;
55106
- }
55107
- function formatInvalidControlByteMessage(hit, locator) {
55108
- return {
55109
- message: `${locator}: ${hit.path} contains literal U+0000 byte at position ${hit.position}, ` + "which PostgreSQL `text` cannot store (SQLSTATE 22P05).",
55110
- hint: "Strip NUL bytes before submit, e.g. `.replace(/\\u0000/g, '')`, or pass --allow-nul-bytes to send anyway."
55111
- };
55112
- }
55113
- function assertNoNulBytes(data, locator) {
55114
- const hit = findInvalidControlByte(data);
55115
- if (!hit)
55116
- return;
55117
- const { message, hint } = formatInvalidControlByteMessage(hit, locator);
55118
- throw new CliError(2 /* UserInput */, "USER_INPUT", message, undefined, hint);
55119
- }
55120
-
55121
55175
  // ../../packages/warmhub-cli/src/domains/commit-submit-template.ts
55122
55176
  import { writeFile } from "node:fs/promises";
55123
55177
  var WRITE_TEMPLATE_KINDS = ["thing", "assertion"];
@@ -55369,7 +55423,7 @@ function buildRetractOperations(input) {
55369
55423
  }
55370
55424
  function rejectLegacyLifecycleOperations(operations) {
55371
55425
  const legacy = operations.find((op) => {
55372
- const operation = op.operation;
55426
+ const operation = op?.operation;
55373
55427
  return operation === "remove" || operation === "deactivate";
55374
55428
  });
55375
55429
  if (!legacy)
@@ -55457,12 +55511,91 @@ function requireCommitSourceValue(source, value) {
55457
55511
  return value;
55458
55512
  throw new Error(`Missing value for selected commit operation source ${source}`);
55459
55513
  }
55514
+ function assertSubmitDeliveryFlags(args) {
55515
+ if (args.dryRun)
55516
+ return;
55517
+ const streamed = args.streamInput || args.jsonlFile;
55518
+ if (args.chunkSize !== undefined && !streamed) {
55519
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "--chunk-size requires --stream or a .jsonl --file.");
55520
+ }
55521
+ if (args.progressRequested && !streamed) {
55522
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "--progress requires --stream or a .jsonl --file.");
55523
+ }
55524
+ if (args.streamId !== undefined && !streamed) {
55525
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "--stream-id requires --stream or a .jsonl --file.");
55526
+ }
55527
+ if (args.timingOut !== undefined && !args.jsonlFile) {
55528
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "--timing-out requires a .jsonl --file.", undefined, "wh commit submit --file ops.jsonl --timing-out timing.json --stream-id import-1 --skip-existing -m 'Import operations' --repo acme/world");
55529
+ }
55530
+ if (streamed && args.streamId === undefined) {
55531
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "JSONL streaming requires --stream-id.", undefined, 'Example: wh commit submit --file ops.jsonl --stream-id bulk-2026-06-04 --skip-existing -m "Bulk import" --repo acme/world');
55532
+ }
55533
+ if (streamed && !args.skipExisting) {
55534
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "JSONL streaming requires --skip-existing.", undefined, 'Example: wh commit submit --file ops.jsonl --stream-id bulk-2026-06-04 --skip-existing -m "Bulk import" --repo acme/world');
55535
+ }
55536
+ }
55460
55537
 
55461
55538
  // ../../packages/warmhub-cli/src/domains/commit-submit-stream.ts
55462
55539
  import { createReadStream as createReadStream2 } from "node:fs";
55463
55540
  import { writeFile as writeFile2 } from "node:fs/promises";
55464
55541
  import { createInterface as createInterface2 } from "node:readline";
55465
55542
 
55543
+ // ../../packages/warmhub-cli/src/commit-payload-validate.ts
55544
+ var NUL = String.fromCharCode(0);
55545
+ function findInvalidControlByte(data, rootLabel = "data") {
55546
+ const segs = [];
55547
+ const pos = visit(data, segs);
55548
+ return pos === -1 ? null : { path: buildPath(rootLabel, segs), position: pos };
55549
+ }
55550
+ function visit(value, segs) {
55551
+ if (typeof value === "string")
55552
+ return value.indexOf(NUL);
55553
+ if (value === null || typeof value !== "object")
55554
+ return -1;
55555
+ if (Array.isArray(value)) {
55556
+ for (let i = 0;i < value.length; i++) {
55557
+ segs.push(i);
55558
+ const p = visit(value[i], segs);
55559
+ if (p !== -1)
55560
+ return p;
55561
+ segs.pop();
55562
+ }
55563
+ return -1;
55564
+ }
55565
+ const keys = Object.keys(value);
55566
+ for (let i = 0;i < keys.length; i++) {
55567
+ const k = keys[i];
55568
+ segs.push(k);
55569
+ const ki = k.indexOf(NUL);
55570
+ if (ki !== -1)
55571
+ return ki;
55572
+ const p = visit(value[k], segs);
55573
+ if (p !== -1)
55574
+ return p;
55575
+ segs.pop();
55576
+ }
55577
+ return -1;
55578
+ }
55579
+ function buildPath(root, segs) {
55580
+ let out = root;
55581
+ for (const s of segs)
55582
+ out += typeof s === "number" ? `[${s}]` : `.${s}`;
55583
+ return out;
55584
+ }
55585
+ function formatInvalidControlByteMessage(hit, locator) {
55586
+ return {
55587
+ message: `${locator}: ${hit.path} contains literal U+0000 byte at position ${hit.position}, ` + "which PostgreSQL `text` cannot store (SQLSTATE 22P05).",
55588
+ hint: "Strip NUL bytes before submit, e.g. `.replace(/\\u0000/g, '')`, or pass --allow-nul-bytes to send anyway."
55589
+ };
55590
+ }
55591
+ function assertNoNulBytes(data, locator) {
55592
+ const hit = findInvalidControlByte(data);
55593
+ if (!hit)
55594
+ return;
55595
+ const { message, hint } = formatInvalidControlByteMessage(hit, locator);
55596
+ throw new CliError(2 /* UserInput */, "USER_INPUT", message, undefined, hint);
55597
+ }
55598
+
55466
55599
  // ../../packages/warmhub-cli/src/domains/commit-submit-utils.ts
55467
55600
  var DEFAULT_JSONL_STREAM_CHUNK_SIZE = Math.max(1, Math.min(MAX_STREAM_APPEND_OPERATION_COUNT, Number(process.env.WH_STREAM_CHUNK_SIZE) || DEFAULT_STREAM_APPEND_CHUNK_SIZE));
55468
55601
  function resolveJsonlStreamChunkSize(chunkSize) {
@@ -55713,9 +55846,9 @@ async function applyJsonlCommit(ctx, args) {
55713
55846
  createdByEmail = appendResult.createdByEmail ?? createdByEmail;
55714
55847
  repoSeq = appendResult.repoSeq ?? repoSeq;
55715
55848
  aggregate.addChunk({
55716
- results: appendResult.results,
55849
+ result: appendResult,
55717
55850
  chunkStart,
55718
- submittedNames: operationsChunk
55851
+ submittedOperations: operationsChunk
55719
55852
  });
55720
55853
  } catch (cause) {
55721
55854
  const pendingOutcome = isDefiniteStreamAppendRejection(cause) ? "absent" : "unknown";
@@ -55894,6 +56027,219 @@ function isErrnoException(error51) {
55894
56027
  return typeof code === "string" && /^E[A-Z0-9]+$/.test(code) && typeof syscall === "string";
55895
56028
  }
55896
56029
 
56030
+ // ../../packages/warmhub-cli/src/domains/commit-validation-execute.ts
56031
+ import { writeFile as writeFile3 } from "node:fs/promises";
56032
+
56033
+ // ../../packages/warmhub-cli/src/domains/commit-validation-input.ts
56034
+ import { createReadStream as createReadStream3 } from "node:fs";
56035
+ import { readFile as readFile2 } from "node:fs/promises";
56036
+ import { createInterface as createInterface3 } from "node:readline";
56037
+ async function readValidationJsonlFile(path2, allowNulBytes) {
56038
+ try {
56039
+ return await readValidationJsonl(createReadStream3(path2, { encoding: "utf-8" }), "--file", allowNulBytes, `Operations file '${path2}' did not contain any JSONL operations`);
56040
+ } catch (error51) {
56041
+ if (isErrnoException(error51)) {
56042
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `Cannot read file '${path2}': ${error51.message}`, undefined, "Check that the file path is correct and the file exists.");
56043
+ }
56044
+ throw error51;
56045
+ }
56046
+ }
56047
+ async function readValidationJsonlStdin(ctx, allowNulBytes) {
56048
+ const input = ctx.stdin ?? process.stdin;
56049
+ if (isTTY(input)) {
56050
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "No JSONL operations provided on stdin.", undefined, "Pipe JSONL operations via stdin: producer | wh commit submit --stream --dry-run --repo acme/world");
56051
+ }
56052
+ return readValidationJsonl(input, "--stream", allowNulBytes, "No JSONL operations provided on stdin.");
56053
+ }
56054
+ async function readValidationJsonl(input, label, allowNulBytes, emptyMessage) {
56055
+ const operations = [];
56056
+ const lines = createInterface3({ input, crlfDelay: Infinity });
56057
+ let lineNumber = 0;
56058
+ try {
56059
+ for await (const rawLine of lines) {
56060
+ lineNumber += 1;
56061
+ const trimmed = rawLine.trim();
56062
+ if (!trimmed)
56063
+ continue;
56064
+ assertValidationOperationCount(operations.length + 1);
56065
+ const operation = safeParseJson(trimmed, `${label} JSONL line ${lineNumber}`);
56066
+ assertStructurallyValidOperation(operation, `${label} JSONL line ${lineNumber}`);
56067
+ rejectLegacyLifecycleOperations([operation]);
56068
+ assertValidationOperationData(operation, `${label} JSONL line ${lineNumber}`, allowNulBytes);
56069
+ operations.push(operation);
56070
+ }
56071
+ } finally {
56072
+ lines.close();
56073
+ }
56074
+ if (operations.length === 0) {
56075
+ throw new CliError(2 /* UserInput */, "USER_INPUT", emptyMessage);
56076
+ }
56077
+ return operations;
56078
+ }
56079
+ async function readValidationJsonArrayFile(path2) {
56080
+ try {
56081
+ return await readFile2(path2, "utf-8");
56082
+ } catch (error51) {
56083
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `Cannot read file '${path2}': ${error51 instanceof Error ? error51.message : String(error51)}`, undefined, "Check that the file path is correct and the file exists.");
56084
+ }
56085
+ }
56086
+ function assertValidationOperationData(operation, source, allowNulBytes) {
56087
+ if (allowNulBytes)
56088
+ return;
56089
+ assertNoNulBytes("data" in operation ? operation.data : undefined, source);
56090
+ }
56091
+ function assertPreparedCommitOperations(args) {
56092
+ if (args.streamInput && !args.dryRun || args.operations.length === 0)
56093
+ return;
56094
+ rejectLegacyLifecycleOperations(args.operations);
56095
+ const source = args.operationSource === "--file" ? args.opsFile : args.operationSource;
56096
+ if (args.dryRun) {
56097
+ for (const [index, operation] of args.operations.entries()) {
56098
+ assertStructurallyValidOperation(operation, `${source} op ${index}`);
56099
+ }
56100
+ }
56101
+ if (args.dryRun)
56102
+ assertValidationOperationCount(args.operations.length);
56103
+ else
56104
+ assertWithinStreamOpLimit(args.operations.length);
56105
+ if (args.allowNulBytes)
56106
+ return;
56107
+ for (const [index, operation] of args.operations.entries()) {
56108
+ const namePart = operation.name ? ` (${operation.name})` : "";
56109
+ assertValidationOperationData(operation, `${source} op ${index}${namePart}`, false);
56110
+ }
56111
+ }
56112
+ function assertStructurallyValidOperation(operation, source) {
56113
+ try {
56114
+ createCommitValidateInput("_", "_", [operation]);
56115
+ } catch (error51) {
56116
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `${source}: ${error51 instanceof Error ? error51.message : String(error51)}`);
56117
+ }
56118
+ }
56119
+ function assertValidationOperationCount(operationCount) {
56120
+ if (operationCount <= MAX_COMMIT_VALIDATION_OPERATIONS)
56121
+ return;
56122
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `Commit validation accepts at most ${MAX_COMMIT_VALIDATION_OPERATIONS} operations (${operationCount} requested).`, undefined, "Split the preview into smaller current-commit units.");
56123
+ }
56124
+ function createBoundedCommitValidateInput(org, repo, operations, options) {
56125
+ assertValidationOperationCount(operations.length);
56126
+ const input = createCommitValidateInput(org, repo, operations, options);
56127
+ const encodedBytes = commitValidateRequestBodyBytes(input);
56128
+ if (encodedBytes > MAX_COMMIT_VALIDATION_ENCODED_BYTES) {
56129
+ throw new CliError(2 /* UserInput */, "USER_INPUT", `Commit validation request exceeds the ${MAX_COMMIT_VALIDATION_ENCODED_BYTES}-byte encoded limit (${encodedBytes} bytes).`, undefined, "Split the preview into smaller current-commit units.");
56130
+ }
56131
+ return input;
56132
+ }
56133
+
56134
+ // ../../packages/warmhub-cli/src/domains/commit-validation-render.ts
56135
+ function renderCommitValidation(ctx, result) {
56136
+ if (ctx.format === "json") {
56137
+ ctx.out(JSON.stringify(result));
56138
+ } else if (ctx.format === "jsonl") {
56139
+ for (const operation of result.operations) {
56140
+ ctx.out(JSON.stringify({ type: "operation", ...operation }));
56141
+ }
56142
+ const { operations: _operations, ...summary } = result;
56143
+ ctx.out(JSON.stringify({ type: "summary", ...summary }));
56144
+ } else {
56145
+ renderPrettyValidation(ctx, result);
56146
+ }
56147
+ const error51 = containedDiagnosticError(validationDiagnostics(result));
56148
+ if (error51)
56149
+ ctx.requestedExitCode = error51.code;
56150
+ }
56151
+ function renderPrettyValidation(ctx, result) {
56152
+ const c = ctx.colors;
56153
+ const baseline = result.baseline.kind === "repo_seq" ? `repo sequence ${result.baseline.repoSeq}` : "withheld";
56154
+ ctx.out(`${c.dim}validation baseline${c.reset} ${baseline}`);
56155
+ for (const caveat of result.caveats) {
56156
+ ctx.out(` ${ctx.chars.warn} ${c.dim}${caveat.code}${c.reset} ${caveat.message}`);
56157
+ }
56158
+ for (const operation of result.operations) {
56159
+ const marker = markerFor(operation.status, ctx);
56160
+ ctx.out(`${marker} [${operation.opIndex}] ${operation.operation} ${displayName(c, operation.name)} ${operation.status}`);
56161
+ renderDiagnostics(ctx, operation.status === "error" ? operation.errors : undefined);
56162
+ for (const effect of operation.effects ?? []) {
56163
+ renderEffect(ctx, effect);
56164
+ }
56165
+ }
56166
+ ctx.out(`${c.bold}${result.canCommit ? "can commit" : "cannot commit"}${c.reset} — ${result.counts.wouldApply} would apply, ${result.counts.noop} noop, ${result.counts.error} error`);
56167
+ }
56168
+ function renderEffect(ctx, effect) {
56169
+ ctx.out(` ${markerFor(effect.status, ctx)} effect ${effect.effectIndex}: ${effect.operation} ${displayName(ctx.colors, effect.name)} ${effect.status}`);
56170
+ renderDiagnostics(ctx, "errors" in effect ? effect.errors : undefined, 6);
56171
+ }
56172
+ function renderDiagnostics(ctx, diagnostics, indent = 4) {
56173
+ for (const diagnostic of diagnostics ?? []) {
56174
+ ctx.out(`${" ".repeat(indent)}${diagnostic.code}: ${diagnostic.message}`);
56175
+ }
56176
+ }
56177
+ function markerFor(status, ctx) {
56178
+ if (status === "error" || status === "discarded")
56179
+ return ctx.chars.cross;
56180
+ if (status === "noop")
56181
+ return ctx.chars.warn;
56182
+ return ctx.chars.check;
56183
+ }
56184
+ function validationDiagnostics(result) {
56185
+ const diagnostics = [];
56186
+ for (const operation of result.operations) {
56187
+ if (operation.status === "error")
56188
+ diagnostics.push(...operation.errors);
56189
+ for (const effect of operation.effects ?? []) {
56190
+ if ("errors" in effect)
56191
+ diagnostics.push(...effect.errors);
56192
+ }
56193
+ }
56194
+ return diagnostics;
56195
+ }
56196
+
56197
+ // ../../packages/warmhub-cli/src/domains/commit-validation-execute.ts
56198
+ async function executeCommitValidation(ctx, args) {
56199
+ const options = {
56200
+ ...args.message !== undefined ? { message: args.message } : {},
56201
+ ...args.committer !== undefined ? { committer: args.committer } : {},
56202
+ ...args.skipExisting ? { skipExisting: true } : {},
56203
+ ...args.includeWouldBeBody ? { includeWouldBeBody: true } : {},
56204
+ ...ctx.signal ? { signal: ctx.signal } : {}
56205
+ };
56206
+ createBoundedCommitValidateInput(args.org, args.repo, args.operations, options);
56207
+ const unusedDeliveryControls = [
56208
+ args.chunkSize !== undefined ? "--chunk-size" : undefined,
56209
+ args.streamId !== undefined ? "--stream-id" : undefined,
56210
+ args.submissionId !== undefined ? "--submission-id" : undefined,
56211
+ args.returnRepoSeq ? "--return-repo-seq" : undefined
56212
+ ].filter((value) => value !== undefined);
56213
+ if (unusedDeliveryControls.length > 0) {
56214
+ ctx.err(`dry-run validates one complete request; delivery controls are unused: ${unusedDeliveryControls.join(", ")}`);
56215
+ }
56216
+ if (args.progressRequested) {
56217
+ ctx.err("validation: waiting for server evaluation");
56218
+ }
56219
+ const startedAt = performance.now();
56220
+ const validation = await ctx.client.commit.validate(args.org, args.repo, args.operations, options);
56221
+ const validationMs = performance.now() - startedAt;
56222
+ if (args.progressRequested)
56223
+ ctx.err("validation: complete");
56224
+ if (args.timingOut) {
56225
+ try {
56226
+ await writeFile3(args.timingOut, JSON.stringify({
56227
+ benchmarkId: process.env.WH_BENCHMARK_ID ?? undefined,
56228
+ org: args.org,
56229
+ repo: args.repo,
56230
+ opCount: args.operations.length,
56231
+ chunkCount: 1,
56232
+ validationMs,
56233
+ totalMs: validationMs,
56234
+ phases: { validationMs }
56235
+ }));
56236
+ } catch (error51) {
56237
+ ctx.err(` warn: failed to write timing sidecar: ${String(error51)}`);
56238
+ }
56239
+ }
56240
+ renderCommitValidation(ctx, validation);
56241
+ }
56242
+
55897
56243
  // ../../packages/warmhub-cli/src/domains/commit-submit-handler.ts
55898
56244
  var handleSubmit = async (ctx, { flags, args }) => {
55899
56245
  const { org, repo } = parseOrgRepo(getRepoRef(ctx) ?? args[0], ctx.config);
@@ -55909,7 +56255,10 @@ var handleSubmit = async (ctx, { flags, args }) => {
55909
56255
  const allowNulBytes = flags["allow-nul-bytes"] === true;
55910
56256
  const timingOut = flags["timing-out"];
55911
56257
  const streamId = flags["stream-id"];
55912
- const submissionId = flags["submission-id"] ?? createOperationEventSubmissionId();
56258
+ const submissionIdFlag = flags["submission-id"];
56259
+ if (flags["include-would-be-body"] === true && !ctx.dryRun) {
56260
+ throw new CliError(2 /* UserInput */, "USER_INPUT", "--include-would-be-body requires --dry-run.", undefined, "Add --dry-run to preview projected bodies, or remove --include-would-be-body for a real commit.");
56261
+ }
55913
56262
  const addNames = flags.add ?? [];
55914
56263
  const reviseName = flags.revise;
55915
56264
  const retractNames = flags.retract ?? [];
@@ -55970,27 +56319,22 @@ var handleSubmit = async (ctx, { flags, args }) => {
55970
56319
  name: flags.name !== undefined,
55971
56320
  members: flags.members !== undefined
55972
56321
  });
55973
- if (chunkSize !== undefined && !streamInput && !jsonlFile) {
55974
- throw new CliError(2 /* UserInput */, "USER_INPUT", "--chunk-size requires --stream or a .jsonl --file.");
55975
- }
55976
- if (progressRequested && !streamInput && !jsonlFile) {
55977
- throw new CliError(2 /* UserInput */, "USER_INPUT", "--progress requires --stream or a .jsonl --file.");
55978
- }
55979
- if (streamId !== undefined && !streamInput && !jsonlFile) {
55980
- throw new CliError(2 /* UserInput */, "USER_INPUT", "--stream-id requires --stream or a .jsonl --file.");
55981
- }
55982
- if (timingOut !== undefined && !jsonlFile) {
55983
- throw new CliError(2 /* UserInput */, "USER_INPUT", "--timing-out requires a .jsonl --file.", undefined, "wh commit submit --file ops.jsonl --timing-out timing.json --stream-id import-1 --skip-existing -m 'Import operations' --repo acme/world");
55984
- }
55985
- if ((streamInput || jsonlFile) && streamId === undefined) {
55986
- throw new CliError(2 /* UserInput */, "USER_INPUT", "JSONL streaming requires --stream-id.", undefined, 'Example: wh commit submit --file ops.jsonl --stream-id bulk-2026-06-04 --skip-existing -m "Bulk import" --repo acme/world');
55987
- }
55988
- if ((streamInput || jsonlFile) && !skipExisting) {
55989
- throw new CliError(2 /* UserInput */, "USER_INPUT", "JSONL streaming requires --skip-existing.", undefined, 'Example: wh commit submit --file ops.jsonl --stream-id bulk-2026-06-04 --skip-existing -m "Bulk import" --repo acme/world');
55990
- }
56322
+ assertSubmitDeliveryFlags({
56323
+ dryRun: ctx.dryRun === true,
56324
+ streamInput,
56325
+ jsonlFile,
56326
+ chunkSize,
56327
+ progressRequested,
56328
+ streamId,
56329
+ timingOut,
56330
+ skipExisting
56331
+ });
55991
56332
  let operations;
56333
+ if (ctx.dryRun && progressRequested) {
56334
+ ctx.err("validation: acquiring operations");
56335
+ }
55992
56336
  if (operationSource === "--stream") {
55993
- operations = [];
56337
+ operations = ctx.dryRun ? await readValidationJsonlStdin(ctx, allowNulBytes) : [];
55994
56338
  } else if (operationSource === "--type") {
55995
56339
  const selectedCollectionType = requireCommitSourceValue(operationSource, collectionType);
55996
56340
  if (!flags.name) {
@@ -56026,14 +56370,9 @@ var handleSubmit = async (ctx, { flags, args }) => {
56026
56370
  } else if (operationSource === "--file") {
56027
56371
  const selectedOpsFile = requireCommitSourceValue(operationSource, opsFile);
56028
56372
  if (selectedOpsFile.endsWith(".jsonl")) {
56029
- operations = [];
56373
+ operations = ctx.dryRun ? await readValidationJsonlFile(selectedOpsFile, allowNulBytes) : [];
56030
56374
  } else {
56031
- let file2;
56032
- try {
56033
- file2 = await readFile2(selectedOpsFile, "utf-8");
56034
- } catch (e) {
56035
- throw new CliError(2 /* UserInput */, "USER_INPUT", `Cannot read file '${selectedOpsFile}': ${e instanceof Error ? e.message : String(e)}`, undefined, "Check that the file path is correct and the file exists.");
56036
- }
56375
+ const file2 = await readValidationJsonArrayFile(selectedOpsFile);
56037
56376
  operations = parseJsonArray(file2, "--file contents");
56038
56377
  }
56039
56378
  } else if (operationSource === "--add") {
@@ -56081,19 +56420,34 @@ var handleSubmit = async (ctx, { flags, args }) => {
56081
56420
  } else {
56082
56421
  operations = [];
56083
56422
  }
56084
- if (!streamInput && operations.length > 0) {
56085
- rejectLegacyLifecycleOperations(operations);
56086
- assertWithinStreamOpLimit(operations.length);
56087
- if (!allowNulBytes) {
56088
- const source = operationSource === "--file" ? opsFile : operationSource;
56089
- for (let i = 0;i < operations.length; i++) {
56090
- const op = operations[i];
56091
- const namePart = op.name ? ` (${op.name})` : "";
56092
- assertNoNulBytes("data" in op ? op.data : undefined, `${source} op ${i}${namePart}`);
56093
- }
56094
- }
56095
- }
56423
+ assertPreparedCommitOperations({
56424
+ operations,
56425
+ operationSource,
56426
+ opsFile,
56427
+ streamInput,
56428
+ dryRun: ctx.dryRun === true,
56429
+ allowNulBytes
56430
+ });
56096
56431
  const message = streamInput || jsonlFile ? messageFlag : messageFlag ?? synthesizeCommitMessage(operations);
56432
+ if (ctx.dryRun) {
56433
+ await executeCommitValidation(ctx, {
56434
+ org,
56435
+ repo,
56436
+ operations,
56437
+ message,
56438
+ committer,
56439
+ skipExisting,
56440
+ includeWouldBeBody: flags["include-would-be-body"] === true,
56441
+ progressRequested,
56442
+ timingOut,
56443
+ chunkSize,
56444
+ streamId,
56445
+ submissionId: submissionIdFlag,
56446
+ returnRepoSeq: returnRepoSeq === true
56447
+ });
56448
+ return;
56449
+ }
56450
+ const submissionId = submissionIdFlag ?? createOperationEventSubmissionId();
56097
56451
  if (streamInput || jsonlFile) {
56098
56452
  ctx.err(`submission ${submissionId}`);
56099
56453
  }
@@ -56178,6 +56532,7 @@ var COMMIT_DOMAIN = defineDomain({
56178
56532
  },
56179
56533
  submit: {
56180
56534
  prime: true,
56535
+ dryRunBehavior: "execute",
56181
56536
  summary: "Submit write operations",
56182
56537
  args: "",
56183
56538
  flags: createFlags3,
@@ -56185,6 +56540,7 @@ var COMMIT_DOMAIN = defineDomain({
56185
56540
  `wh commit submit --add player --shape location --data '{"x":0,"y":0}'`,
56186
56541
  `wh commit submit --add alice --data '{"score":1}' --add bob --data '{"score":2}' --shape Player -m "seed players"`,
56187
56542
  'wh commit submit -f operations.json -m "Batch update"',
56543
+ "wh commit submit -f operations.json --dry-run --format jsonl",
56188
56544
  `wh commit submit --ops '[{"operation":"add","kind":"thing","name":"Session/run-001","data":{}},{"operation":"add","kind":"assertion","name":"HypothesisCandidate/run-001-claim","about":"Session/run-001","data":{}}]' -m "Create session + assertion"`,
56189
56545
  `printf '%s\\n' '{"operation":"add","kind":"thing","name":"Player/alice","data":{"score":1}}' | wh commit submit --stream --stream-id bulk-2026-06-04 --skip-existing --repo acme/world -m "stdin stream"`,
56190
56546
  'wh commit submit --file dataset.jsonl --stream-id bulk-2026-06-04 --skip-existing --progress -m "bulk stream"',
@@ -56194,6 +56550,7 @@ var COMMIT_DOMAIN = defineDomain({
56194
56550
  'wh commit submit --type set --name active-locations --members Location/a,Location/b,Location/c -m "Create location set"'
56195
56551
  ],
56196
56552
  notes: [
56553
+ "`--dry-run` evaluates the complete bounded input with the real server commit evaluator and makes no durable repository change.",
56197
56554
  "Need to build an ops file? Run `wh shape template <Shape>` to scaffold the JSON from a shape definition, edit the FILL_IN placeholders, then pass it to `--file`.",
56198
56555
  "Inspect a shape's fields first with `wh thing view <Shape>` before authoring or editing an ops file."
56199
56556
  ],
@@ -60126,7 +60483,7 @@ var ORG_DOMAIN = defineDomain({
60126
60483
  });
60127
60484
 
60128
60485
  // ../../packages/warmhub-cli/src/domains/prime-content.md
60129
- var prime_content_default = "# WarmHub CLI Context\n> **Context Recovery**: Run `wh prime` after compaction or new session\n\n## Environment\n{{REPO_LINE}}\n\n## Core Concepts\n- **Thing**: A named entity versioned by writes. **Assertion**: A thing that makes a shape-validated claim about another thing.\n- **Shape**: A thing defining data structure; every other thing has one. **Write**: One or more add/revise/retract operations with per-operation results.\n- **wref**: A reference to a thing. Local: `Player` (the shape) or `Player/alice` (a thing with that shape). Cross-repo: `wh:org/repo/Shape` or `wh:org/repo/Shape/name`.\n\n## Versioned Wrefs\n- `Shape` addresses the shape itself; `Shape/name` addresses a thing with that shape. `@vN` pins either.\n- Floating write refs to retracted targets fail; existing pinned versions remain valid.\n- Rename invalidates old spellings, including `@vN`; the new name resolves history.\n- Untyped wrefs accept any thing. `wref<T>` requires the target's shape to be `T`; a shape has no shape, so never satisfies it. `wref?` coalesces only `thing_absent`, never a missing shape.\n\n## Key Workflows\n\n**Write data** (discover shapes → scaffold ops → submit):\n```bash\nwh shape list --repo org/repo # list available shapes\nwh shape view ShapeName --repo org/repo # inspect fields\nwh shape template ShapeName -o ops.json --repo org/repo # scaffold write ops (supports --kind assertion)\n# edit ops.json — fill FILL_IN placeholders — then:\nwh commit submit --file ops.json -m \"msg\" --repo org/repo # submit operations (bare `wh commit` also works)\n# or single assertion (no file needed):\nwh assertion create --shape ShapeName --about Target/name --name my-assertion --data '{\"field\":1}' --repo org/repo\n# Relay failed operation details when present. Never hand-guess ops JSON — use `wh shape template <Shape>`.\n```\n\n**Read data:**\n```bash\nwh thing list --repo org/repo # all things at HEAD\nwh thing view Shape/name --repo org/repo # inspect a thing\nwh thing query --shape MyShape --repo org/repo # find things by shape\nwh thing about Shape --repo org/repo # assertions about a shape\nwh assertion list --repo org/repo # all assertions at HEAD\nwh thing history Shape/name --repo org/repo # version history\n\n# Batch read — wh thing view is variadic (max 500 wrefs/call):\nwh thing view Player/alice Player/bob # variadic positionals\nwh thing view --file wrefs.txt --json # one wref per line\ncat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped* requested wref\n```\n\n## Wref Quick Reference\n\nWrites use explicit names and wrefs. Untyped fields, collection members,\nassertion `about`, and committers accept any thing. Create a deterministic\ntarget before referencing it in the same commit.\n\n## Command Reference\n\n**Global flags**: `--repo`, `--format`, `--json`, `--live`\n### thing — Things\n- `wh thing list [--shape] [--kind] [--match] [--include-retracted]` — Current HEAD state\n- `wh thing view [<wref>...] [--file <path>] [--version] [--depth] [--include-retracted] [--data-mode auto|full]` — Thing details. Variadic (max 500). `--version` implies `--include-retracted`. Batch JSON returns `{ requested, items, missing }`; jsonl emits one row per deduped requested wref. Large Set/List bodies summarize by default; use `--data-mode full` for canonical collection JSON.\n- `wh thing history [wref] [--shape] [--about] [--include-retracted]` — Version history\n- `wh thing resolve <wref>` — Resolve a wref to its canonical thing identity\n- `wh thing create <name|Shape/name> (--data|--file) [--shape]` — Create\n- `wh thing revise <name> [--data] [--message] [--committer] [--expected-version]` — Revise (CONFLICT if HEAD≠n)\n- `wh thing retract <wref> -m <message> [--reason] [--kind] [--expected-version]` — Retract\n- `wh thing query [--shape] [--kind] [--about] [--match]` — Query by filters\n- `wh thing search <query> [--shape] [--kind] [--about] [--mode]` — Search text\n- `wh thing rename <Shape/oldName> <newName>` — Rename\n- `wh thing refs <wref> [--inbound] [--outbound] [--field]` — Show field references; use `wh thing about` for assertions about the target thing\n- `wh thing about <wref> [--shape] [--match] [--depth] [--resolve-collections] [--role from|to|ends] [--limit] [--include-retracted]` — Show assertions about the target identity; `--resolve-collections` expands bare/@HEAD/@ALL members, `--role` filters direction; not pinned @vN\n\n- `wh view evaluate VIEW [--limit N] [--cursor TOK] [--all]`\n### commit — Write operations\n- `wh commit submit [--ops|--file|--add|--revise|--retract] [--kind] [--data] [--reason] [--expected-version] [--chunk-size] [--skip-existing] [--progress] [--stream-id]` — Submit operations (bare `wh commit` is equivalent). Use `--file ops.jsonl --stream-id <id> --chunk-size 5000 --skip-existing` for bulk ingest.\n- `wh shape template <shape> [shape2 ...] [--kind] [--operation add|revise|retract] [-o file]` — Generate sample ops\n\n### assertion — Assertion operations\n- `wh assertion list [--about wref] [--shape] [--match] [--include-retracted]` — Browse assertions\n- `wh assertion view <wref> [--version] [--include-retracted]` — Assertion details\n- `wh assertion create --shape <s> --name <n> --about <wref> [--data] [-m] [--committer]` — Create assertion\n- `wh assertion revise <wref> --data <json> [--message] [--committer]` — Revise assertion\n- `wh assertion retract <wref> -m <message> [--reason] [--committer] [--expected-version]` — Retract assertion\n- `wh assertion history <wref> [--include-retracted]` — Assertion history\n\n### shape — Shape management\n- `wh shape list [--match] [--include-retracted]` — List all shapes\n- `wh shape view <name> [--include-retracted]` — Shape details\n- `wh shape revise <name> (--fields|--file)`\n- `wh shape create <name> (--fields|--file)`\n- `wh shape retract <name> -m <message> [--reason] [--expected-version]` — Retract shape\n- `wh shape history <name> [--include-retracted]` — Shape history\n- `wh shape rename <oldName> <newName>` — Rename shape\n\n### repo — Repository management\n- `wh repo create <org/name> [--display-name] [--description] [--visibility]` — Create repo\n- `wh repo list [org]` — List repos\n- `wh repo view [org/repo]` — Repo details\n\n### org — Organization management\n- `wh org create <name> [--display-name]` — Create a new organization\n- `wh org view <name>` — View organization details (alias: info)\n- `wh org list` — List all organizations\n\n### sub — Subscription management\n- `wh sub create <name> [flags]` — Create a subscription\n- `wh sub view <name>` — View subscription details\n- `wh sub list` — List all subscriptions\n- `wh sub log <name>` — Tail subscription delivery feed\n- `wh sub attempts <runId>` — Show attempt history for a run\n- `wh sub pause <name>` — Pause a subscription\n- `wh sub resume <name>` — Resume a paused subscription\n- `wh sub bind <name> [--credentials]` — Bind a credential set to a subscription for webhook auth\n- `wh sub unbind <name>` — Remove credential binding from a subscription\n- `wh sub delete <name>` — Retire; name reserved\n\n### notifications — Action notification listing\n- `wh notifications [--limit] [--since]` — List repo-scoped action notifications\n\n### credential — Credential set management\n- `wh credential create <name> [--repo org/repo | --org org] [--scope] [--description]` — Create an empty credential set\n- `wh credential list [--repo org/repo | --org org]` — List credential sets accessible from a repo or org\n- `wh credential view <name> [--repo org/repo | --org org]` — View a credential set (key names only, no values)\n- `wh credential delete <name> [--repo org/repo | --org org]` — Delete a credential set and its Vault object\n- `wh credential set <setName> [<keyName>] [--repo org/repo | --org org] [--value]` — Set credential key(s). With `<keyName>`: single-key form (reads value from `--value` or stdin). Without `<keyName>`: batch form (reads JSON object from stdin, e.g. `{\"KEY\":\"val\"}`)\n- `wh credential unset <setName> <keyName> [--repo org/repo | --org org]` — Remove a key from a credential set\n- `wh credential audit <setName> [--repo org/repo | --org org]` — View audit log for a credential set\n- `wh credential revoke <setName> [--repo org/repo | --org org] [--reason]` — Revoke a credential set (blocks new binds and stops bound webhook deliveries)\n\n### component — Component management\n- `wh component validate <path>` — Validate package\n- `wh component install <org/name>` — Install a registered component\n- `wh component register <name> --org <org> --manifest <path> [flags]` — Register component identity\n- `wh component unregister <org/name>` — Remove a registered component identity\n- `wh component registry list --org <org>` — List registered components\n- `wh component registry view <org/name>` — View a registered component\n- `wh component registry update <org/name> [flags]` — Update a registered component\n- `wh component list` — List installed components\n- `wh component update <org/name>` — Update installed component\n- `wh component view <org/name>` — Show component details (alias: show)\n- `wh component doctor <org/name>` — Run component health checks\n- `wh component teardown <org/name>` — Pause component subscriptions\n\n### Getting More Info\n- `wh help` — help overview\n- `wh <domain>` — domain verbs\n- `wh <domain> <verb> --help` — verb flags and examples\n- `wh help --format json` — `CliSpecV2` (`schemaVersion: 2`): global/contextual/root tables and command overrides\n\n## Common Workflows\n\n**Explore a repo:**\n```bash\nwh thing list --repo org/repo # see all things in HEAD\nwh thing view Shape/name --repo org/repo # inspect a specific thing\nwh thing history Shape/name --repo org/repo # inspect version history\nwh thing about Shape/name # assertions about thing/shape\n```\n\n**Create an assertion** (most common write):\n```bash\n# --about takes an untyped target wref: Shape or Shape/name.\nwh assertion create --shape MyShape --about TargetShape/target-name \\\n --name my-assertion --data '{\"field_a\":1,\"field_b\":\"value\"}' --repo org/repo\n# Output includes per-operation status; relay failures when present.\n```\n\n**Create via write entrypoint** (alternative, supports batches and streams):\n```bash\nwh commit submit --add my-item --shape MyShape --kind assertion \\\n --about TargetShape/target-name --data '{\"field_a\":1}' --repo org/repo\n```\n\n**Batch write via file** (generate template → edit → submit):\n```bash\nwh shape template Hypothesis Evidence -o ops.json # add --kind assertion --about TargetShape/name for assertions\n# edit ops.json — fill FILL_IN placeholders\nwh commit submit --file ops.json -m \"batch update\" # submit all operations (bare `wh commit` is equivalent)\n# --file format: docs.warmhub.ai/cli-reference/commit-operations\n```\n\n**Bulk write (JSONL stream, >1k ops)** — one chunked stream beats per-record commits (~60× faster):\n```bash\nwh shape template MyShape -o ops.jsonl # one op per line (.jsonl)\nID=\"bulk-$(date +%s)\" # caller id for chunk correlation\nwh commit submit --file ops.jsonl --stream-id \"$ID\" --chunk-size 5000 \\\n --skip-existing --progress -m \"bulk ingest\" --repo org/repo\n# --chunk-size: ops per append chunk (default 1000, max 10000); chunk≈1 is ~60× slower\n# --skip-existing: skips already-written add ops (drops per-row read-before-write)\n# --stream-id identifies the submission but provides no receipt or resume.\n# After an outcome-unknown append, stop writes and reconcile attempted + unsent\n# work from a later verified checkpoint; never blindly replay revise/retract.\n```\n\n**Create collections:**\n```bash\nwh commit submit --type arc --name route --members Location/a,Location/b --repo org/repo\nwh assertion create --shape Supports --name route-support --about Arc/route --data '{\"confidence\":0.8}' --repo org/repo\n```\n\n**Modify data:**\n```bash\nwh thing revise Shape/name --data '{\"x\":5,\"y\":3}' -m \"update\" --repo org/repo\nwh thing retract Shape/old-item -m \"withdrawn\" --reason \"data feed contaminated\" --repo org/repo\n```\n\n**Query and filter:**\n```bash\nwh thing query --shape MyShape # by shape\nwh thing query --kind assertion --about Shape/name # by kind + target\nwh thing history Shape/name --limit 10 # version history\n```\n\n## Built-in Content shape\n\nWarmHub repos expose three well-known content wrefs:\n- `Content/Readme` — stored markdown for humans\n- `Content/Agents` — stored markdown guidance for AI agents\n- `Content/LlmsTxt` — synthesized per-request sitemap (read-only)\n\nFetch via `wh repo content get --kind readme|agents|llms-txt`,\n`client.repo.getReadme/getAgents/getLlmsTxt`, MCP `warmhub_repo_content_get`,\nor raw HTTP `GET /{org}/{repo}/readme.md|agents.md|llms.txt`.\nSee `wh repo describe` → `additionalInformation` for the discovery field.\n\n## Query Discipline\n- Plan the repo, shapes, and wrefs you need before the first query.\n- Gather the needed facts from one repo before switching to another.\n- Do the queries first, then write one complete answer.\n\n## Agent Tips\n- **Always run commands for live data** — this context describes the CLI, not repo contents\n- **Before writing, discover wrefs** — run `wh thing list` or `wh shape list`\n- **Shape field types**: `string`, `number`, `boolean`, `wref`, arrays, optionals, nested objects\n- **Write commands return per-operation results** — relay failures and affected wrefs to the user\n- **Pass data inline** with `--data '{...}'` — do NOT create temp files\n- Add `--json` to any command for machine-readable JSON output\n- **Aliases teach canonical commands** — executing aliases emit a status hint; misleading lifecycle aliases reject and point to `retract`\n- Writes go through `wh commit submit` (or bare `wh commit`) or wrappers (`create`, `revise`, `retract`, `assertion create`). `retract` irreversibly withdraws an identity and creates a history entry.\n- Use `wh doctor` to check environment health\n";
60486
+ var prime_content_default = "# WarmHub CLI Context\n> **Context Recovery**: Run `wh prime` after compaction or new session\n\n## Environment\n{{REPO_LINE}}\n\n## Core Concepts\n- **Thing**: A named entity versioned by writes. **Assertion**: A thing that makes a shape-validated claim about another thing.\n- **Shape**: A thing defining data structure; every other thing has one. **Write**: One or more add/revise/retract operations with per-operation results.\n- **wref**: A reference to a thing. Local: `Player` (the shape) or `Player/alice` (a thing with that shape). Cross-repo: `wh:org/repo/Shape` or `wh:org/repo/Shape/name`.\n\n## Versioned Wrefs\n- `Shape` addresses the shape itself; `Shape/name` addresses a thing with that shape. `@vN` pins either.\n- Floating write refs to retracted targets fail; existing pinned versions remain valid.\n- Rename invalidates old spellings, including `@vN`; the new name resolves history.\n- Untyped wrefs accept any thing. `wref<T>` requires the target's shape to be `T`; a shape has no shape, so never satisfies it. `wref?` coalesces only `thing_absent`, never a missing shape.\n\n## Key Workflows\n\n**Write data** (discover shapes → scaffold ops → submit):\n```bash\nwh shape list --repo org/repo # list available shapes\nwh shape view ShapeName --repo org/repo # inspect fields\nwh shape template ShapeName -o ops.json --repo org/repo # scaffold write ops (supports --kind assertion)\n# edit ops.json — fill FILL_IN placeholders — then:\nwh commit submit --file ops.json --dry-run --repo org/repo # preview; remove --dry-run to submit\n# or single assertion (no file needed):\nwh assertion create --shape ShapeName --about Target/name --name my-assertion --data '{\"field\":1}' --repo org/repo\n# Relay failed operation details when present. Never hand-guess ops JSON — use `wh shape template <Shape>`.\n```\n\n**Read data:**\n```bash\nwh thing list --repo org/repo # all things at HEAD\nwh thing view Shape/name --repo org/repo # inspect a thing\nwh thing query --shape MyShape --repo org/repo # find things by shape\nwh thing about Shape --repo org/repo # assertions about a shape\nwh assertion list --repo org/repo # all assertions at HEAD\nwh thing history Shape/name --repo org/repo # version history\n\n# Batch read — wh thing view is variadic (max 500 wrefs/call):\nwh thing view Player/alice Player/bob # variadic positionals\nwh thing view --file wrefs.txt --json # one wref per line\ncat wrefs.txt | wh thing view --format jsonl # one JSON line per *deduped* requested wref\n```\n\n## Wref Quick Reference\n\nWrites use explicit names and wrefs. Untyped fields, collection members,\nassertion `about`, and committers accept any thing. Create a deterministic\ntarget before referencing it in the same commit.\n\n## Command Reference\n\n**Global flags**: `--repo`, `--format`, `--json`, `--live`\n### thing — Things\n- `wh thing list [--shape] [--kind] [--match] [--include-retracted]` — Current HEAD state\n- `wh thing view [<wref>...] [--file <path>] [--version] [--depth] [--include-retracted] [--data-mode auto|full]` — Thing details. Variadic (max 500). `--version` implies `--include-retracted`. Batch JSON returns `{ requested, items, missing }`; jsonl emits one row per deduped requested wref. Large Set/List bodies summarize by default; use `--data-mode full` for canonical collection JSON.\n- `wh thing history [wref] [--shape] [--about] [--include-retracted]` — Version history\n- `wh thing resolve <wref>` — Resolve a wref to its canonical thing identity\n- `wh thing create <name|Shape/name> (--data|--file) [--shape]` — Create\n- `wh thing revise <name> [--data] [--message] [--committer] [--expected-version]` — Revise (CONFLICT if HEAD≠n)\n- `wh thing retract <wref> -m <message> [--reason] [--kind] [--expected-version]` — Retract\n- `wh thing query [--shape] [--kind] [--about] [--match]` — Query by filters\n- `wh thing search <query> [--shape] [--kind] [--about] [--mode]` — Search text\n- `wh thing rename <Shape/oldName> <newName>` — Rename\n- `wh thing refs <wref> [--inbound] [--outbound] [--field]` — Show field references; use `wh thing about` for assertions about the target thing\n- `wh thing about <wref> [--shape] [--match] [--depth] [--resolve-collections] [--role from|to|ends] [--limit] [--include-retracted]` — Show assertions about the target identity; `--resolve-collections` expands bare/@HEAD/@ALL members, `--role` filters direction; not pinned @vN\n\n- `wh view evaluate VIEW [--limit N] [--cursor TOK] [--all]`\n### commit — Write operations\n- `wh commit submit [--ops|--file|--add|--revise|--retract] [--dry-run] [--skip-existing]` — Submit operations, or evaluate the complete bounded input without durable changes under `--dry-run`. JSONL preview emits one operation row per input plus one summary.\n- `wh shape template <shape> [shape2 ...] [--kind] [--operation add|revise|retract] [-o file]` — Generate sample ops\n\n### assertion — Assertion operations\n- `wh assertion list [--about wref] [--shape] [--match] [--include-retracted]` — Browse assertions\n- `wh assertion view <wref> [--version] [--include-retracted]` — Assertion details\n- `wh assertion create --shape <s> --name <n> --about <wref> [--data] [-m] [--committer]` — Create assertion\n- `wh assertion revise <wref> --data <json> [--message] [--committer]` — Revise assertion\n- `wh assertion retract <wref> -m <message> [--reason] [--committer] [--expected-version]` — Retract assertion\n- `wh assertion history <wref> [--include-retracted]` — Assertion history\n\n### shape — Shape management\n- `wh shape list [--match] [--include-retracted]` — List all shapes\n- `wh shape view <name> [--include-retracted]` — Shape details\n- `wh shape revise <name> (--fields|--file)`\n- `wh shape create <name> (--fields|--file)`\n- `wh shape retract <name> -m <message> [--reason] [--expected-version]` — Retract shape\n- `wh shape history <name> [--include-retracted]` — Shape history\n- `wh shape rename <oldName> <newName>` — Rename shape\n\n### repo — Repository management\n- `wh repo create <org/name> [--display-name] [--description] [--visibility]` — Create repo\n- `wh repo list [org]` — List repos\n- `wh repo view [org/repo]` — Repo details\n\n### org — Organization management\n- `wh org create <name> [--display-name]` — Create a new organization\n- `wh org view <name>` — View organization details (alias: info)\n- `wh org list` — List all organizations\n\n### sub — Subscription management\n- `wh sub create <name> [flags]` — Create a subscription\n- `wh sub view <name>` — View subscription details\n- `wh sub list` — List all subscriptions\n- `wh sub log <name>` — Tail subscription delivery feed\n- `wh sub attempts <runId>` — Show attempt history for a run\n- `wh sub pause <name>` — Pause a subscription\n- `wh sub resume <name>` — Resume a paused subscription\n- `wh sub bind <name> [--credentials]` — Bind a credential set to a subscription for webhook auth\n- `wh sub unbind <name>` — Remove credential binding from a subscription\n- `wh sub delete <name>` — Retire; name reserved\n\n### notifications — Action notification listing\n- `wh notifications [--limit] [--since]` — List repo-scoped action notifications\n\n### credential — Credential set management\n- `wh credential create <name> [--repo org/repo | --org org] [--scope] [--description]` — Create an empty credential set\n- `wh credential list [--repo org/repo | --org org]` — List credential sets accessible from a repo or org\n- `wh credential view <name> [--repo org/repo | --org org]` — View a credential set (key names only, no values)\n- `wh credential delete <name> [--repo org/repo | --org org]` — Delete a credential set and its Vault object\n- `wh credential set <setName> [<keyName>] [--repo org/repo | --org org] [--value]` — Set credential key(s). With `<keyName>`: single-key form (reads value from `--value` or stdin). Without `<keyName>`: batch form (reads JSON object from stdin, e.g. `{\"KEY\":\"val\"}`)\n- `wh credential unset <setName> <keyName> [--repo org/repo | --org org]` — Remove a key from a credential set\n- `wh credential audit <setName> [--repo org/repo | --org org]` — View audit log for a credential set\n- `wh credential revoke <setName> [--repo org/repo | --org org] [--reason]` — Revoke a credential set (blocks new binds and stops bound webhook deliveries)\n\n### component — Component management\n- `wh component validate <path>` — Validate package\n- `wh component install <org/name>` — Install a registered component\n- `wh component register <name> --org <org> --manifest <path> [flags]` — Register component identity\n- `wh component unregister <org/name>` — Remove a registered component identity\n- `wh component registry list --org <org>` — List registered components\n- `wh component registry view <org/name>` — View a registered component\n- `wh component registry update <org/name> [flags]` — Update a registered component\n- `wh component list` — List installed components\n- `wh component update <org/name>` — Update installed component\n- `wh component view <org/name>` — Show component details (alias: show)\n- `wh component doctor <org/name>` — Run component health checks\n- `wh component teardown <org/name>` — Pause component subscriptions\n\n### Getting More Info\n- `wh help` — help overview\n- `wh <domain>` — domain verbs\n- `wh <domain> <verb> --help` — verb flags and examples\n- `wh help --format json` — `CliSpecV2` (`schemaVersion: 2`): global/contextual/root tables and command overrides\n\n## Common Workflows\n\n**Explore a repo:**\n```bash\nwh thing list --repo org/repo # see all things in HEAD\nwh thing view Shape/name --repo org/repo # inspect a specific thing\nwh thing history Shape/name --repo org/repo # inspect version history\nwh thing about Shape/name # assertions about thing/shape\n```\n\n**Create an assertion** (most common write):\n```bash\n# --about takes an untyped target wref: Shape or Shape/name.\nwh assertion create --shape MyShape --about TargetShape/target-name \\\n --name my-assertion --data '{\"field_a\":1,\"field_b\":\"value\"}' --repo org/repo\n# Output includes per-operation status; relay failures when present.\n```\n\n**Create via write entrypoint** (alternative, supports batches and streams):\n```bash\nwh commit submit --add my-item --shape MyShape --kind assertion \\\n --about TargetShape/target-name --data '{\"field_a\":1}' --repo org/repo\n```\n\n**Batch write via file** (generate template → edit → submit):\n```bash\nwh shape template Hypothesis Evidence -o ops.json # add --kind assertion --about TargetShape/name for assertions\n# edit ops.json — fill FILL_IN placeholders\nwh commit submit --file ops.json -m \"batch update\" # submit all operations (bare `wh commit` is equivalent)\n# --file format: docs.warmhub.ai/cli-reference/commit-operations\n```\n\n**Bulk write (JSONL stream, >1k ops)** — one chunked stream beats per-record commits (~60× faster):\n```bash\nwh shape template MyShape -o ops.jsonl # one op per line (.jsonl)\nID=\"bulk-$(date +%s)\" # caller id for chunk correlation\nwh commit submit --file ops.jsonl --stream-id \"$ID\" --chunk-size 5000 \\\n --skip-existing --progress -m \"bulk ingest\" --repo org/repo\n# --chunk-size: ops per append chunk (default 1000, max 10000); chunk≈1 is ~60× slower\n# --skip-existing: skips already-written add ops (drops per-row read-before-write)\n# --stream-id identifies the submission but provides no receipt or resume.\n# After an outcome-unknown append, stop writes and reconcile attempted + unsent\n# work from a later verified checkpoint; never blindly replay revise/retract.\n```\n\n**Create collections:**\n```bash\nwh commit submit --type arc --name route --members Location/a,Location/b --repo org/repo\nwh assertion create --shape Supports --name route-support --about Arc/route --data '{\"confidence\":0.8}' --repo org/repo\n```\n\n**Modify data:**\n```bash\nwh thing revise Shape/name --data '{\"x\":5,\"y\":3}' -m \"update\" --repo org/repo\nwh thing retract Shape/old-item -m \"withdrawn\" --reason \"data feed contaminated\" --repo org/repo\n```\n\n**Query and filter:**\n```bash\nwh thing query --shape MyShape # by shape\nwh thing query --kind assertion --about Shape/name # by kind + target\nwh thing history Shape/name --limit 10 # version history\n```\n\n## Built-in Content shape\n\nWarmHub repos expose three well-known content wrefs:\n- `Content/Readme` — stored markdown for humans\n- `Content/Agents` — stored markdown guidance for AI agents\n- `Content/LlmsTxt` — synthesized per-request sitemap (read-only)\n\nFetch via `wh repo content get --kind readme|agents|llms-txt`,\n`client.repo.getReadme/getAgents/getLlmsTxt`, MCP `warmhub_repo_content_get`,\nor raw HTTP `GET /{org}/{repo}/readme.md|agents.md|llms.txt`.\nSee `wh repo describe` → `additionalInformation` for the discovery field.\n\n## Query Discipline\n- Plan the repo, shapes, and wrefs you need before the first query.\n- Gather the needed facts from one repo before switching to another.\n- Do the queries first, then write one complete answer.\n\n## Agent Tips\n- **Always run commands for live data** — this context describes the CLI, not repo contents\n- **Before writing, discover wrefs** — run `wh thing list` or `wh shape list`\n- **Shape field types**: `string`, `number`, `boolean`, `wref`, arrays, optionals, nested objects\n- **Write commands return per-operation results** — relay failures and affected wrefs to the user\n- **Pass data inline** with `--data '{...}'` — do NOT create temp files\n- Add `--json` to any command for machine-readable JSON output\n- **Aliases teach canonical commands** — executing aliases emit a status hint; misleading lifecycle aliases reject and point to `retract`\n- Writes go through `wh commit submit` (or bare `wh commit`) or wrappers (`create`, `revise`, `retract`, `assertion create`). `retract` irreversibly withdraws an identity and creates a history entry.\n- Use `wh doctor` to check environment health\n";
60130
60487
 
60131
60488
  // ../../packages/warmhub-cli/src/domains/prime.ts
60132
60489
  function buildMarkdown(config2) {
@@ -60199,7 +60556,7 @@ var PRIME_DOMAIN = defineDomain({
60199
60556
  });
60200
60557
 
60201
60558
  // ../../packages/warmhub-cli/src/domains/repo/checkpoint.ts
60202
- import { createReadStream as createReadStream4 } from "node:fs";
60559
+ import { createReadStream as createReadStream5 } from "node:fs";
60203
60560
  import { lstat as lstat3 } from "node:fs/promises";
60204
60561
 
60205
60562
  // ../../packages/sdk-ts/src/repository-checkpoint/types.ts
@@ -60574,7 +60931,7 @@ import { createHash as createHash3 } from "node:crypto";
60574
60931
 
60575
60932
  // ../../packages/sdk-ts/src/repository-checkpoint/identity-sort.ts
60576
60933
  import { once } from "node:events";
60577
- import { createReadStream as createReadStream3, createWriteStream } from "node:fs";
60934
+ import { createReadStream as createReadStream4, createWriteStream } from "node:fs";
60578
60935
  import { open as open3, rm } from "node:fs/promises";
60579
60936
  import { join as join10 } from "node:path";
60580
60937
  var CHECKPOINT_IDENTITY_SORT_BUDGET_BYTES = 4 * 1024 * 1024;
@@ -60848,7 +61205,7 @@ class RunCursor {
60848
61205
  #offset = 0;
60849
61206
  current;
60850
61207
  constructor(path2) {
60851
- this.#stream = createReadStream3(path2, {
61208
+ this.#stream = createReadStream4(path2, {
60852
61209
  highWaterMark: RUN_READ_BUFFER_BYTES
60853
61210
  });
60854
61211
  this.#iterator = this.#stream[Symbol.asyncIterator]();
@@ -61496,7 +61853,7 @@ var handleVerify = async (ctx, { args }) => {
61496
61853
  }
61497
61854
  try {
61498
61855
  await lstat3(archive);
61499
- const result = await verifyRepositoryCheckpointArchive(createReadStream4(archive));
61856
+ const result = await verifyRepositoryCheckpointArchive(createReadStream5(archive));
61500
61857
  writeOutput(ctx, result, () => {
61501
61858
  ctx.out(`Checkpoint: ${result.checkpointId}`);
61502
61859
  ctx.out(`Repository sequence: ${result.repoSeq}`);
@@ -64897,7 +65254,7 @@ async function dispatchDomain(ctx, invocation, dispatchRegistry = productionRegi
64897
65254
  ]);
64898
65255
  return;
64899
65256
  }
64900
- if (ctx.dryRun) {
65257
+ if (ctx.dryRun && pathSpec.dryRunBehavior === "dispatch") {
64901
65258
  emitDryRun(ctx, invocation, undefined, pathSpec.flags);
64902
65259
  return;
64903
65260
  }
@@ -64939,7 +65296,7 @@ async function dispatchDomain(ctx, invocation, dispatchRegistry = productionRegi
64939
65296
  if (!handler) {
64940
65297
  throw new CliError(1 /* Runtime */, "UNKNOWN", `No handler registered for ${invocation.commandPath.join(".")}`);
64941
65298
  }
64942
- if (ctx.dryRun) {
65299
+ if (ctx.dryRun && verbSpec.dryRunBehavior === "dispatch") {
64943
65300
  emitDryRun(ctx, invocation, canonicalVerb, verbSpec.flags);
64944
65301
  return;
64945
65302
  }
@@ -65750,7 +66107,8 @@ function resolveCommand(records, resolver, sourceRecords = records) {
65750
66107
  name: "help",
65751
66108
  summary: "Show command help",
65752
66109
  args: "[domain]",
65753
- flags: [...HELP_FLAGS2]
66110
+ flags: [...HELP_FLAGS2],
66111
+ dryRunBehavior: "dispatch"
65754
66112
  },
65755
66113
  flags: HELP_FLAGS2,
65756
66114
  args: "[domain]",
@@ -66319,7 +66677,7 @@ async function runPreparedCli(rawArgv, prepared, opts) {
66319
66677
  });
66320
66678
  const chars = makeChars();
66321
66679
  const signal = abortController.signal;
66322
- await dispatch({
66680
+ const commandContext = {
66323
66681
  client,
66324
66682
  config: config2,
66325
66683
  invocation,
@@ -66347,8 +66705,9 @@ async function runPreparedCli(rawArgv, prepared, opts) {
66347
66705
  signal,
66348
66706
  rawFetch: opts?.rawFetch ?? fetch,
66349
66707
  confirm: (message) => confirmPrompt(message, { signal })
66350
- });
66351
- exitCode = cancelledExitCode ?? 0 /* Ok */;
66708
+ };
66709
+ await dispatch(commandContext);
66710
+ exitCode = cancelledExitCode ?? commandContext.requestedExitCode ?? 0 /* Ok */;
66352
66711
  return exitCode;
66353
66712
  } catch (error51) {
66354
66713
  if (cancelledExitCode !== undefined) {
@@ -66442,7 +66801,7 @@ function resolveLogLevel(flagLevel, env) {
66442
66801
  // package.json
66443
66802
  var package_default3 = {
66444
66803
  name: "@warmhub/cli",
66445
- version: "0.93.0",
66804
+ version: "0.95.0",
66446
66805
  private: false,
66447
66806
  type: "module",
66448
66807
  description: "The wh CLI for WarmHub — create repos, commit and query data, and compound knowledge with your AI agents.",
@@ -67061,5 +67420,5 @@ process.exitCode = interceptedExitCode === undefined ? await runPreparedCli(laun
67061
67420
  version: package_default3.version
67062
67421
  }) : interceptedExitCode;
67063
67422
 
67064
- //# debugId=A6E050347C8F0F7D64756E2164756E21
67065
- //# warmhub-cli-build-info {"cliVersion":"0.93.0","sdkVersion":"0.91.0"}
67423
+ //# debugId=E38898F8C9FABDFA64756E2164756E21
67424
+ //# warmhub-cli-build-info {"cliVersion":"0.95.0","sdkVersion":"0.93.0"}