primitive-admin 1.1.0-alpha.41 → 1.1.0-alpha.42

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.
@@ -37,9 +37,9 @@ export declare function computeExpandedContentHash(parsed: any): string;
37
37
  */
38
38
  export declare function shouldPushExpandedFile(parsed: any, storedHash: string | undefined): boolean;
39
39
  /**
40
- * Normalize a parsed workflow TOML object so that any of the five
41
- * serializer-emitted fields the *local* file omits are filled in with the
42
- * model default. Used by `diff` so a hand-authored workflow that omits e.g.
40
+ * Normalize a parsed workflow TOML object so that any of the
41
+ * serializer-emitted default fields the *local* file omits are filled in with
42
+ * the model default. Used by `diff` so a hand-authored workflow that omits e.g.
43
43
  * `perUserMaxRunning` compares equal to a server that defaulted it to 4
44
44
  * (the #1175 false-positive guard) — while a server value that was
45
45
  * *explicitly set* to a non-default (e.g. 8) still shows as Modified.
@@ -113,11 +113,6 @@ export declare function fetchAll<T>(listFn: (params: {
113
113
  items: T[];
114
114
  nextCursor?: string | null;
115
115
  }>, pageSize?: number, maxPages?: number): Promise<T[]>;
116
- /**
117
- * Safely parse a JSON field that may be a string or already an object.
118
- * TOML can parse inline tables as objects, so we need to handle both.
119
- */
120
- export declare function safeJsonParse(value: any): any;
121
116
  /**
122
117
  * Resolve a key-based rule set name reference to an ID.
123
118
  * Throws if the name cannot be resolved and throwOnMissing is true.
@@ -13,6 +13,7 @@ import { createSnapshot, listSnapshots, resolveSnapshot, restoreSnapshot, pruneS
13
13
  import { validateWorkflowToml, formatWorkflowTomlErrors, } from "../lib/workflow-toml-validator.js";
14
14
  import { expandWorkflowTomlData } from "../lib/workflow-fragments.js";
15
15
  import { applyWorkflowBody } from "../lib/workflow-apply.js";
16
+ import { buildWorkflowPayloadFromToml } from "../lib/workflow-payload.js";
16
17
  import { detectLayout, migrateLegacyToV2 } from "../lib/block-layout.js";
17
18
  import { success, error, printApiError, info, warn, keyValue, json, divider, } from "../lib/output.js";
18
19
  import { confirmPrompt } from "../lib/confirm-prompt.js";
@@ -135,30 +136,43 @@ export function shouldPushExpandedFile(parsed, storedHash) {
135
136
  return computeExpandedContentHash(parsed) !== storedHash;
136
137
  }
137
138
  /**
138
- * Model defaults for the *exact five* workflow-level fields that
139
- * `serializeWorkflow` emits unconditionally (`sync.ts` workflow serializer:
140
- * `perUserMaxRunning`, `perUserMaxQueued`, `dequeueOrder`,
141
- * `requiresClientApply`, `syncCallable`). These mirror `models.yaml`
142
- * (`WorkflowDefinition`): `perUserMaxRunning=4`, `perUserMaxQueued=100`,
143
- * `dequeueOrder="fifo"`, `requiresClientApply=true`, `syncCallable=false`.
139
+ * Model defaults for the workflow-level fields that `serializeWorkflow` emits
140
+ * with a concrete server default the *local* file may omit (`sync.ts` workflow
141
+ * serializer): `perUserMaxRunning`, `perUserMaxQueued`, `perAppMaxRunning`,
142
+ * `perAppMaxQueued`, `queueTtlSeconds`, `dequeueOrder`, `requiresClientApply`,
143
+ * `syncCallable`. These mirror `models.yaml` (`WorkflowDefinition`):
144
+ * `perUserMaxRunning=4`, `perUserMaxQueued=100`, `perAppMaxRunning=25`,
145
+ * `perAppMaxQueued=10000`, `queueTtlSeconds=43200`, `dequeueOrder="fifo"`,
146
+ * `requiresClientApply=true`, `syncCallable=false`.
147
+ *
148
+ * `perAppMax*` / `queueTtlSeconds` were added here for #1177: once the pull
149
+ * serializer began emitting them (they carry a non-null model default, so a GET
150
+ * always returns them), an unchanged workflow using the server defaults hashed
151
+ * unequal to a local TOML that omits them — a false `modified` diff that made
152
+ * `sync pull` rewrite the file just to inject defaults. Normalizing both sides
153
+ * to the model default here closes that gap the same way the per-user limits do.
144
154
  *
145
155
  * This is deliberately NOT a generic models.yaml default importer — the model
146
- * carries many defaults the serializer never writes (`perAppMax*`,
147
- * `queueTtlSeconds`, …). Only the serializer-emitted surface matters for a
148
- * content comparison, because the remote side is hashed via `serializeWorkflow`
149
- * and therefore only ever contains these five.
156
+ * carries defaults the serializer never writes. Only the serializer-emitted
157
+ * surface with a concrete default matters for a content comparison, because the
158
+ * remote side is hashed via `serializeWorkflow`. Emit-when-set fields whose GET
159
+ * value is null/empty when unset (`accessRule`, `runAs`, `capabilities`) are
160
+ * omitted on both sides and so need no normalization here.
150
161
  */
151
162
  const WORKFLOW_SERIALIZER_DEFAULTS = {
152
163
  perUserMaxRunning: 4,
153
164
  perUserMaxQueued: 100,
165
+ perAppMaxRunning: 25,
166
+ perAppMaxQueued: 10000,
167
+ queueTtlSeconds: 43200,
154
168
  dequeueOrder: "fifo",
155
169
  requiresClientApply: true,
156
170
  syncCallable: false,
157
171
  };
158
172
  /**
159
- * Normalize a parsed workflow TOML object so that any of the five
160
- * serializer-emitted fields the *local* file omits are filled in with the
161
- * model default. Used by `diff` so a hand-authored workflow that omits e.g.
173
+ * Normalize a parsed workflow TOML object so that any of the
174
+ * serializer-emitted default fields the *local* file omits are filled in with
175
+ * the model default. Used by `diff` so a hand-authored workflow that omits e.g.
162
176
  * `perUserMaxRunning` compares equal to a server that defaulted it to 4
163
177
  * (the #1175 false-positive guard) — while a server value that was
164
178
  * *explicitly set* to a non-default (e.g. 8) still shows as Modified.
@@ -488,6 +502,13 @@ export function serializeWorkflow(workflow, draft, configs) {
488
502
  : undefined,
489
503
  perUserMaxRunning: workflow.perUserMaxRunning,
490
504
  perUserMaxQueued: workflow.perUserMaxQueued,
505
+ // #1177 — app-level queue limits + queue TTL. Emit only when set so a
506
+ // fresh pull → push round-trips them without writing noisy zero/empty
507
+ // keys. The shared payload builder forwards these on create AND update,
508
+ // so the pull serializer must surface them for the round-trip to close.
509
+ perAppMaxRunning: workflow.perAppMaxRunning ?? undefined,
510
+ perAppMaxQueued: workflow.perAppMaxQueued ?? undefined,
511
+ queueTtlSeconds: workflow.queueTtlSeconds ?? undefined,
491
512
  dequeueOrder: workflow.dequeueOrder,
492
513
  // Sync-callable flags (#807): always emit both so a fresh pull → push
493
514
  // cycle round-trips them. The GET response normalizes these to booleans
@@ -768,17 +789,6 @@ export async function fetchAll(listFn, pageSize = 100, maxPages = 100) {
768
789
  } while (cursor);
769
790
  return all;
770
791
  }
771
- /**
772
- * Safely parse a JSON field that may be a string or already an object.
773
- * TOML can parse inline tables as objects, so we need to handle both.
774
- */
775
- export function safeJsonParse(value) {
776
- if (value === undefined || value === null)
777
- return undefined;
778
- if (typeof value === "string")
779
- return JSON.parse(value);
780
- return value; // Already an object (TOML parsed it as a table)
781
- }
782
792
  /**
783
793
  * Resolve a key-based rule set name reference to an ID.
784
794
  * Throws if the name cannot be resolved and throwOnMissing is true.
@@ -3085,25 +3095,19 @@ Directory Structure:
3085
3095
  changes.push({ type: "workflow", action: "create", key });
3086
3096
  if (!options.dryRun) {
3087
3097
  try {
3098
+ // #1177: build the `[workflow]`-derived fields through the
3099
+ // shared builder so `sync push` create can't drift from
3100
+ // `create --from-file` / the update path (this branch used to
3101
+ // drop `status`, `capabilities`, `perAppMax*`, `queueTtlSeconds`).
3102
+ // Structural fields (`workflowKey`, `steps`, `metadataManifest`)
3103
+ // and the manifest-key `name` fallback are attached here; the
3104
+ // `name` override follows the spread so it wins over the
3105
+ // builder's bare `name`.
3088
3106
  const created = await client.createWorkflow(resolvedAppId, {
3089
3107
  workflowKey: key,
3090
- name: workflow.name || key,
3091
- description: workflow.description,
3092
3108
  steps,
3093
- inputSchema: workflow.inputSchema ? safeJsonParse(workflow.inputSchema) : undefined,
3094
- outputSchema: workflow.outputSchema ? safeJsonParse(workflow.outputSchema) : undefined,
3095
- accessRule: workflow.accessRule || undefined,
3096
- // #1081 — workflow principal mode (absent → server default).
3097
- runAs: workflow.runAs || undefined,
3098
- perUserMaxRunning: workflow.perUserMaxRunning,
3099
- perUserMaxQueued: workflow.perUserMaxQueued,
3100
- dequeueOrder: workflow.dequeueOrder,
3101
- // Sync-callable flags (#807): pass through as-is. An absent
3102
- // TOML key is `undefined` (dropped by JSON.stringify), so the
3103
- // server applies its defaults (requiresClientApply=true,
3104
- // syncCallable=false); an explicit value is honored.
3105
- requiresClientApply: workflow.requiresClientApply,
3106
- syncCallable: workflow.syncCallable,
3109
+ ...buildWorkflowPayloadFromToml(workflow, { mode: "create" }),
3110
+ name: workflow.name || key,
3107
3111
  // #1304 (P-C): declared-access manifest (absent → undefined,
3108
3112
  // server default null).
3109
3113
  metadataManifest: workflow.metadataManifest ?? undefined,