primitive-admin 1.0.61 → 1.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -69
- package/assets/skill/skills/primitive-platform/SKILL.md +336 -37
- package/dist/bin/primitive.js +25 -14
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/analytics.js +36 -16
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.js +49 -188
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +13 -153
- package/dist/src/commands/blob-buckets.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +14 -100
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/collections.js +47 -8
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/config.d.ts +46 -0
- package/dist/src/commands/config.js +479 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/connections.js +12 -7
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/cron-triggers.js +84 -176
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-type-configs.d.ts +2 -0
- package/dist/src/commands/database-type-configs.js +171 -0
- package/dist/src/commands/database-type-configs.js.map +1 -0
- package/dist/src/commands/database-types.js +4 -4
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.d.ts +63 -0
- package/dist/src/commands/databases.js +649 -87
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +869 -12
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.js +18 -125
- package/dist/src/commands/email-templates.js.map +1 -1
- package/dist/src/commands/env.d.ts +12 -1
- package/dist/src/commands/env.js +98 -25
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/feature-flags.d.ts +14 -0
- package/dist/src/commands/feature-flags.js +116 -0
- package/dist/src/commands/feature-flags.js.map +1 -0
- package/dist/src/commands/group-type-configs.js +14 -109
- package/dist/src/commands/group-type-configs.js.map +1 -1
- package/dist/src/commands/init.d.ts +9 -1
- package/dist/src/commands/init.js +1056 -277
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +136 -421
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.d.ts +12 -0
- package/dist/src/commands/metadata-category-configs.js +112 -0
- package/dist/src/commands/metadata-category-configs.js.map +1 -0
- package/dist/src/commands/metadata.js +33 -141
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/prompts.js +206 -563
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +1 -1
- package/dist/src/commands/rule-sets.js +16 -132
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.js +67 -234
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sessions.js +17 -5
- package/dist/src/commands/sessions.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +113 -70
- package/dist/src/commands/sync-app-settings.js +193 -242
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +1932 -49
- package/dist/src/commands/sync.js +9521 -1769
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +8 -2
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +92 -1
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/vars.js +44 -58
- package/dist/src/commands/vars.js.map +1 -1
- package/dist/src/commands/webhooks.js +318 -156
- package/dist/src/commands/webhooks.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +49 -12
- package/dist/src/commands/workflows.js +652 -1086
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/access-rule-display.d.ts +21 -0
- package/dist/src/lib/access-rule-display.js +34 -0
- package/dist/src/lib/access-rule-display.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +541 -61
- package/dist/src/lib/api-client.js +559 -102
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +180 -25
- package/dist/src/lib/app-settings-descriptor.js +403 -82
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/canonical-json.d.ts +12 -0
- package/dist/src/lib/canonical-json.js +35 -0
- package/dist/src/lib/canonical-json.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +8 -0
- package/dist/src/lib/cli-manifest.js +1 -0
- package/dist/src/lib/cli-manifest.js.map +1 -1
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +6 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js +9 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +6 -6
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +8 -8
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.d.ts +127 -0
- package/dist/src/lib/config-object-descriptor.js +658 -0
- package/dist/src/lib/config-object-descriptor.js.map +1 -0
- package/dist/src/lib/config-payload.d.ts +85 -0
- package/dist/src/lib/config-payload.js +116 -0
- package/dist/src/lib/config-payload.js.map +1 -0
- package/dist/src/lib/config-surface.d.ts +130 -0
- package/dist/src/lib/config-surface.js +300 -0
- package/dist/src/lib/config-surface.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +10 -0
- package/dist/src/lib/credentials-store.js +23 -0
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/data-input.d.ts +23 -0
- package/dist/src/lib/data-input.js +50 -0
- package/dist/src/lib/data-input.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +147 -0
- package/dist/src/lib/env-resolver-core.js +265 -0
- package/dist/src/lib/env-resolver-core.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +28 -6
- package/dist/src/lib/env-resolver.js +44 -32
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/generated-allowlist.js +12 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
- package/dist/src/lib/generated-config-surfaces.js +4058 -0
- package/dist/src/lib/generated-config-surfaces.js.map +1 -0
- package/dist/src/lib/generated-template-lint.d.ts +212 -0
- package/dist/src/lib/generated-template-lint.js +624 -0
- package/dist/src/lib/generated-template-lint.js.map +1 -0
- package/dist/src/lib/init-adopt.d.ts +16 -0
- package/dist/src/lib/init-adopt.js +34 -0
- package/dist/src/lib/init-adopt.js.map +1 -0
- package/dist/src/lib/init-assets.d.ts +39 -0
- package/dist/src/lib/init-assets.js +97 -0
- package/dist/src/lib/init-assets.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +44 -5
- package/dist/src/lib/init-config.js +80 -7
- package/dist/src/lib/init-config.js.map +1 -1
- package/dist/src/lib/init-ios-links.d.ts +50 -0
- package/dist/src/lib/init-ios-links.js +153 -0
- package/dist/src/lib/init-ios-links.js.map +1 -0
- package/dist/src/lib/init-plan.d.ts +80 -0
- package/dist/src/lib/init-plan.js +95 -0
- package/dist/src/lib/init-plan.js.map +1 -0
- package/dist/src/lib/init-production-env.d.ts +48 -0
- package/dist/src/lib/init-production-env.js +59 -0
- package/dist/src/lib/init-production-env.js.map +1 -0
- package/dist/src/lib/init-schema.d.ts +74 -0
- package/dist/src/lib/init-schema.js +358 -0
- package/dist/src/lib/init-schema.js.map +1 -0
- package/dist/src/lib/init-xcode.d.ts +33 -0
- package/dist/src/lib/init-xcode.js +114 -0
- package/dist/src/lib/init-xcode.js.map +1 -0
- package/dist/src/lib/integration-request-config.d.ts +30 -0
- package/dist/src/lib/integration-request-config.js +145 -0
- package/dist/src/lib/integration-request-config.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +55 -0
- package/dist/src/lib/local-state.js +167 -0
- package/dist/src/lib/local-state.js.map +1 -0
- package/dist/src/lib/log-inspection.d.ts +568 -0
- package/dist/src/lib/log-inspection.js +639 -0
- package/dist/src/lib/log-inspection.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +2 -2
- package/dist/src/lib/migration-nag.js +3 -3
- package/dist/src/lib/object-status-filter.d.ts +22 -0
- package/dist/src/lib/object-status-filter.js +45 -0
- package/dist/src/lib/object-status-filter.js.map +1 -0
- package/dist/src/lib/output.d.ts +24 -1
- package/dist/src/lib/output.js +42 -1
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/package-manager.d.ts +140 -0
- package/dist/src/lib/package-manager.js +305 -0
- package/dist/src/lib/package-manager.js.map +1 -0
- package/dist/src/lib/paginate.d.ts +50 -0
- package/dist/src/lib/paginate.js +53 -0
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/platform-owned.d.ts +29 -3
- package/dist/src/lib/platform-owned.js +42 -3
- package/dist/src/lib/platform-owned.js.map +1 -1
- package/dist/src/lib/project-config.js +13 -5
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/query-operators.d.ts +1 -1
- package/dist/src/lib/query-operators.js +1 -1
- package/dist/src/lib/record-filter.d.ts +18 -0
- package/dist/src/lib/record-filter.js +55 -0
- package/dist/src/lib/record-filter.js.map +1 -0
- package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
- package/dist/src/lib/resolve-init-dev-port.js +55 -0
- package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
- package/dist/src/lib/resolve-init-server.d.ts +64 -0
- package/dist/src/lib/resolve-init-server.js +77 -0
- package/dist/src/lib/resolve-init-server.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +43 -14
- package/dist/src/lib/resolve-platform.js +74 -12
- package/dist/src/lib/resolve-platform.js.map +1 -1
- package/dist/src/lib/run-status.d.ts +19 -0
- package/dist/src/lib/run-status.generated.d.ts +39 -0
- package/dist/src/lib/run-status.generated.js +66 -0
- package/dist/src/lib/run-status.generated.js.map +1 -0
- package/dist/src/lib/run-status.js +19 -0
- package/dist/src/lib/run-status.js.map +1 -0
- package/dist/src/lib/server-text-normalization.d.ts +51 -0
- package/dist/src/lib/server-text-normalization.js +90 -0
- package/dist/src/lib/server-text-normalization.js.map +1 -0
- package/dist/src/lib/server-url.d.ts +22 -0
- package/dist/src/lib/server-url.js +33 -0
- package/dist/src/lib/server-url.js.map +1 -0
- package/dist/src/lib/signing-secret-status.d.ts +81 -0
- package/dist/src/lib/signing-secret-status.js +116 -0
- package/dist/src/lib/signing-secret-status.js.map +1 -0
- package/dist/src/lib/skill-installer.js +6 -3
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/snapshots.d.ts +2 -2
- package/dist/src/lib/snapshots.js +2 -2
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +15 -2
- package/dist/src/lib/swift-codegen/dbGenerator.js +250 -18
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/generator.d.ts +11 -2
- package/dist/src/lib/swift-codegen/generator.js +181 -7
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +7 -3
- package/dist/src/lib/swift-codegen/siblingSymbols.js +4 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -1
- package/dist/src/lib/sync-dir-selector.d.ts +21 -0
- package/dist/src/lib/sync-dir-selector.js +30 -0
- package/dist/src/lib/sync-dir-selector.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +39 -0
- package/dist/src/lib/sync-paths.js +70 -2
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +138 -31
- package/dist/src/lib/sync-resource-types.js +241 -47
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +95 -0
- package/dist/src/lib/sync-selectors.js +228 -0
- package/dist/src/lib/sync-selectors.js.map +1 -0
- package/dist/src/lib/template.d.ts +62 -4
- package/dist/src/lib/template.js +147 -39
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/test-case-keys.d.ts +29 -0
- package/dist/src/lib/test-case-keys.js +55 -0
- package/dist/src/lib/test-case-keys.js.map +1 -0
- package/dist/src/lib/test-case-variables.d.ts +15 -0
- package/dist/src/lib/test-case-variables.js +29 -0
- package/dist/src/lib/test-case-variables.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +4 -4
- package/dist/src/lib/toml-database-config.js +27 -34
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/toml-metadata-config.d.ts +9 -5
- package/dist/src/lib/toml-metadata-config.js +40 -45
- package/dist/src/lib/toml-metadata-config.js.map +1 -1
- package/dist/src/lib/toml-native-form.d.ts +1 -1
- package/dist/src/lib/toml-native-form.js +1 -1
- package/dist/src/lib/toml-params-validator.d.ts +1 -1
- package/dist/src/lib/toml-params-validator.js +1 -1
- package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
- package/dist/src/lib/toml-scalar-edit.js +283 -0
- package/dist/src/lib/toml-scalar-edit.js.map +1 -0
- package/dist/src/lib/user-selector.d.ts +24 -0
- package/dist/src/lib/user-selector.js +33 -0
- package/dist/src/lib/user-selector.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +25 -0
- package/dist/src/lib/version-check.js +77 -8
- package/dist/src/lib/version-check.js.map +1 -1
- package/dist/src/lib/watch.d.ts +121 -0
- package/dist/src/lib/watch.js +169 -0
- package/dist/src/lib/watch.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +69 -45
- package/dist/src/lib/workflow-apply.js +99 -72
- package/dist/src/lib/workflow-apply.js.map +1 -1
- package/dist/src/lib/workflow-codegen/generator.js +4 -4
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +11 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +5 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
- package/dist/src/lib/workflow-config-apply.d.ts +70 -0
- package/dist/src/lib/workflow-config-apply.js +137 -0
- package/dist/src/lib/workflow-config-apply.js.map +1 -0
- package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
- package/dist/src/lib/workflow-config-sidecar.js +96 -0
- package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
- package/dist/src/lib/workflow-defaults.d.ts +29 -0
- package/dist/src/lib/workflow-defaults.js +41 -0
- package/dist/src/lib/workflow-defaults.js.map +1 -0
- package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
- package/dist/src/lib/workflow-include-preserve.js +286 -0
- package/dist/src/lib/workflow-include-preserve.js.map +1 -0
- package/dist/src/lib/workflow-payload.d.ts +31 -20
- package/dist/src/lib/workflow-payload.js +100 -110
- package/dist/src/lib/workflow-payload.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +37 -16
- package/dist/src/lib/workflow-toml-validator.js +141 -16
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +78 -22
- package/dist/src/validators.d.ts +7 -7
- package/dist/src/validators.js +6 -6
- package/package.json +11 -6
- package/dist/src/commands/settings.d.ts +0 -15
- package/dist/src/commands/settings.js +0 -102
- package/dist/src/commands/settings.js.map +0 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { ApiClient } from "../lib/api-client.js";
|
|
3
|
-
import { type
|
|
3
|
+
import { type OperationFormHints } from "../lib/toml-database-config.js";
|
|
4
|
+
import { type FieldForm } from "../lib/toml-native-form.js";
|
|
5
|
+
import { type PushMode } from "../lib/config-payload.js";
|
|
6
|
+
import { type ConfigObjectSurface, type ConfigTable } from "../lib/generated-config-surfaces.js";
|
|
7
|
+
import { type PresenceOutcome, type TestBlockType } from "../lib/sync-resource-types.js";
|
|
8
|
+
import type { SyncState } from "../types/index.js";
|
|
9
|
+
import { type SyncSelection } from "../lib/sync-selectors.js";
|
|
4
10
|
/**
|
|
5
11
|
* Wrap a server-side error so the printed message identifies which entity
|
|
6
12
|
* was in flight. Used by every entity create/update/delete call site in the
|
|
@@ -13,8 +19,30 @@ import { type PresenceOutcome } from "../lib/sync-resource-types.js";
|
|
|
13
19
|
* - Other errors are rewrapped as a new `ApiError` carrying the wrapped
|
|
14
20
|
* message + the original `details[]` and `statusCode`.
|
|
15
21
|
*/
|
|
16
|
-
export declare function wrapEntityError(err: unknown, action: "create" | "update" | "delete", kind: string, key: string): Error;
|
|
22
|
+
export declare function wrapEntityError(err: unknown, action: "create" | "update" | "delete" | "activate", kind: string, key: string): Error;
|
|
23
|
+
/**
|
|
24
|
+
* Attribute a `[[configs]]` failure to the prompt AND the config (#2972).
|
|
25
|
+
*
|
|
26
|
+
* The server describes such a failure in terms of the entry alone ("Config
|
|
27
|
+
* name already exists for this prompt"), and push applies a prompt through
|
|
28
|
+
* several requests, so a run over a tree of prompts printed one bare line the
|
|
29
|
+
* operator could only attribute by reading the create log above it. The pair
|
|
30
|
+
* is the same identity the server stores the entry under (`promptId#configName`).
|
|
31
|
+
*
|
|
32
|
+
* The result is marked `entityNamed`, so the prompt-level wrapper around the
|
|
33
|
+
* shared update body leaves it alone.
|
|
34
|
+
*/
|
|
35
|
+
export declare function wrapPromptConfigError(err: unknown, action: "create" | "update" | "activate", promptKey: string, configName: string | undefined): Error;
|
|
17
36
|
export declare function computeFileHash(filePath: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* A transform's comparison hash: the script body itself (#2731 B7).
|
|
39
|
+
*
|
|
40
|
+
* Unlike the config types there is no projection to do — the `.rhai` bytes are
|
|
41
|
+
* exactly what push sends and exactly what diff compares — so the "semantic"
|
|
42
|
+
* hash is just a stable hash of the body, computable from a server response
|
|
43
|
+
* without going through the file.
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeScriptBodyHash(body: string): string;
|
|
18
46
|
export declare function shouldPushFile(filePath: string, storedHash: string | undefined): boolean;
|
|
19
47
|
/**
|
|
20
48
|
* Hash the *expanded* (post-fragment-splice) content of a workflow TOML.
|
|
@@ -38,17 +66,70 @@ export declare function computeExpandedContentHash(parsed: any): string;
|
|
|
38
66
|
*/
|
|
39
67
|
export declare function shouldPushExpandedFile(parsed: any, storedHash: string | undefined): boolean;
|
|
40
68
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
69
|
+
* The `[workflow].activeConfigName` a file actually states, trimmed — or
|
|
70
|
+
* `undefined` when the file has no opinion about activation.
|
|
71
|
+
*
|
|
72
|
+
* Push reads the key the same way: `applyWorkflowConfigSidecars` activates a
|
|
73
|
+
* configuration only for a truthy name and otherwise leaves the running one
|
|
74
|
+
* alone (`cli/src/lib/workflow-config-apply.ts`). Every diff decision about
|
|
75
|
+
* activation keys off this one predicate so the comparison cannot claim a
|
|
76
|
+
* difference push has no way to reconcile (#2743, review follow-up).
|
|
77
|
+
*/
|
|
78
|
+
export declare function authoredActiveConfigName(parsed: any): string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The configuration a server workflow is RUNNING — its `activeConfigId`, or the
|
|
81
|
+
* first config as the fallback `serializeWorkflow` has always used, so pull's
|
|
82
|
+
* `activeConfigName` and the diff's reading of it can never disagree.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveActiveWorkflowConfig(workflow: any, configs: any[]): any;
|
|
85
|
+
/**
|
|
86
|
+
* Normalize a parsed workflow TOML object into the form the SERVER would hold
|
|
87
|
+
* after a `config push` of that file. Used by `diff` so a hand-authored workflow
|
|
88
|
+
* compares equal to the running state it already describes — while a server
|
|
89
|
+
* value that genuinely differs still shows as Modified.
|
|
90
|
+
*
|
|
91
|
+
* In order:
|
|
92
|
+
* 1. Trim the fields the server trims (`name`, `description`).
|
|
93
|
+
* 2. Drop explicit-empty authored values — a `""` string or an empty
|
|
94
|
+
* `capabilities` array. Push sends these as `null` (or the model default),
|
|
95
|
+
* and the pull serializer omits the key for a cleared field, so "spelled
|
|
96
|
+
* out as empty" and "absent" have to hash the same (#2743).
|
|
97
|
+
* 3. Lowercase the two enums and floor the five queue limits, mirroring the
|
|
98
|
+
* server's accepted-value canonicalization
|
|
99
|
+
* (`src/workflows/config/workflow-field-handlers.ts`). A value the server
|
|
100
|
+
* would REJECT is left alone: push fails loudly for those, so there is
|
|
101
|
+
* nothing to preview. #2743 — without this, `dequeueOrder = "LIFO"` or
|
|
102
|
+
* `perUserMaxRunning = 4.9` pushes fine and then reports Modified forever.
|
|
103
|
+
* 4. Fill the model defaults (`WORKFLOW_MODEL_DEFAULTS`) for what the file
|
|
104
|
+
* omits. `perAppMax*` / `queueTtlSeconds` joined that list in #1177: once
|
|
105
|
+
* the pull serializer emitted them (a GET always returns them, they carry
|
|
106
|
+
* a non-null model default), an unchanged workflow hashed unequal to a
|
|
107
|
+
* local TOML that omits them — a false `modified` that made `config pull`
|
|
108
|
+
* rewrite the file just to inject defaults. (`status` was on this list
|
|
109
|
+
* until #2803 took availability off the config wire entirely.)
|
|
110
|
+
* 5. Fill `activeConfigName` with the name the server itself creates
|
|
111
|
+
* (`default`), so a hand-authored file that never named a config compares
|
|
112
|
+
* equal to the workflow that pushing it produces (#2743). This is the
|
|
113
|
+
* compare-BY-NAME case only: when the local file names no configuration at
|
|
114
|
+
* all, `config diff` hashes both sides with `compareActivation: false` and
|
|
115
|
+
* the fill never decides anything.
|
|
116
|
+
* 6. Fill `key` from `resolvedKey` when the file omits it. `[workflow].key`
|
|
117
|
+
* is optional — push and diff both resolve it from the FILENAME
|
|
118
|
+
* (`tomlData.workflow?.key || basename(file, ".toml")`) — while the pull
|
|
119
|
+
* serializer always emits it. Without this a file relying on the filename
|
|
120
|
+
* hashes unequal to the very workflow it describes and reports Modified
|
|
121
|
+
* forever, which is exactly the non-convergence #2743 removes. Callers
|
|
122
|
+
* that already know the resolved key pass it; the rest leave the key as
|
|
123
|
+
* authored.
|
|
124
|
+
*
|
|
125
|
+
* Only the DIFF-hash path (`hashWorkflowTomlForDiff`) normalizes; the stored
|
|
126
|
+
* content-hash path that push's skip check compares is untouched, so upgrading
|
|
127
|
+
* forces no one-time re-push (same note as #1446's schema canonicalization).
|
|
47
128
|
*
|
|
48
129
|
* Returns a shallow clone with a normalized `workflow` table; the input is
|
|
49
130
|
* not mutated. Non-workflow TOML (no `workflow` table) is returned unchanged.
|
|
50
131
|
*/
|
|
51
|
-
export declare function normalizeWorkflowTomlDefaults(parsed: any): any;
|
|
132
|
+
export declare function normalizeWorkflowTomlDefaults(parsed: any, resolvedKey?: string): any;
|
|
52
133
|
/**
|
|
53
134
|
* Canonical content hash of a workflow TOML for `diff`'s content comparison.
|
|
54
135
|
* Applies `normalizeWorkflowTomlDefaults` first so omitted-vs-defaulted fields
|
|
@@ -56,17 +137,42 @@ export declare function normalizeWorkflowTomlDefaults(parsed: any): any;
|
|
|
56
137
|
* pull stores and push compares. Both the local file and the
|
|
57
138
|
* `serializeWorkflow`-produced remote form flow through this single function,
|
|
58
139
|
* so the two sides are normalized identically by construction.
|
|
140
|
+
*
|
|
141
|
+
* `resolvedKey` is the key the caller already paired the two sides on (the
|
|
142
|
+
* `[workflow].key`, or the filename when the file omits it). Passing it lets a
|
|
143
|
+
* file that relies on the filename fallback hash equal to the remote form,
|
|
144
|
+
* which always spells the key out — see `normalizeWorkflowTomlDefaults`.
|
|
145
|
+
*
|
|
146
|
+
* `compareActivation: false` drops `activeConfigName` from the hash. `config diff`
|
|
147
|
+
* passes it when the local file names no configuration: push activates nothing
|
|
148
|
+
* for such a file, so the configuration the server happens to be running is not
|
|
149
|
+
* a difference push could ever reconcile, and hashing it in would report the
|
|
150
|
+
* workflow Modified after every successful push — the non-convergence #2743
|
|
151
|
+
* exists to remove (review follow-up). The remote name stays visible as a hint
|
|
152
|
+
* on the Synced row instead. Defaults to comparing, so the include-reconcile
|
|
153
|
+
* path (`reconcileWorkflowIncludes`, which asks whether the local file already
|
|
154
|
+
* expands to the running state) keeps seeing activation as content.
|
|
59
155
|
*/
|
|
60
|
-
export declare function hashWorkflowTomlForDiff(parsed: any
|
|
156
|
+
export declare function hashWorkflowTomlForDiff(parsed: any, resolvedKey?: string, options?: {
|
|
157
|
+
compareActivation?: boolean;
|
|
158
|
+
}): string;
|
|
61
159
|
/**
|
|
62
160
|
* Build the canonical content hash for a *server* workflow (as returned by
|
|
63
161
|
* `getWorkflow` + active `getWorkflowConfig`), mirroring exactly what a fresh
|
|
64
|
-
* `
|
|
162
|
+
* `config pull` would write to disk. Serializes via `serializeWorkflow`, parses
|
|
65
163
|
* the resulting TOML (remote serialized TOML never carries `include`s, so a
|
|
66
164
|
* plain `parseConfigToml` is sufficient — no fragment path needed), then hashes
|
|
67
165
|
* through `hashWorkflowTomlForDiff` so it lines up with the local-file hash.
|
|
166
|
+
*
|
|
167
|
+
* `logger` is optional but `config diff` passes it: the serializer's
|
|
168
|
+
* unrecognized-server-key warning (#2644) is the only signal that this CLI
|
|
169
|
+
* version cannot represent a field the server holds, and a diff that swallowed
|
|
170
|
+
* it would report the workflow as in sync with no hint that anything is
|
|
171
|
+
* missing.
|
|
68
172
|
*/
|
|
69
|
-
export declare function hashRemoteWorkflowForDiff(workflow: any, draft: any, configs: any[]
|
|
173
|
+
export declare function hashRemoteWorkflowForDiff(workflow: any, draft: any, configs: any[], logger?: (message: string) => void, options?: {
|
|
174
|
+
compareActivation?: boolean;
|
|
175
|
+
}): string;
|
|
70
176
|
export interface ParsedAuthSettings {
|
|
71
177
|
settings: Record<string, any>;
|
|
72
178
|
warnings: string[];
|
|
@@ -78,8 +184,7 @@ export interface ParsedAuthSettings {
|
|
|
78
184
|
* (`cli/src/lib/app-settings-descriptor.ts`), which now owns the [auth] field
|
|
79
185
|
* set that `AUTH_BOOLEAN_KEYS`/`RECOGNIZED_AUTH_KEYS` used to hard-code. The
|
|
80
186
|
* descriptor drives all four sections in one place, so pull, push, and the
|
|
81
|
-
* unrecognized-key warning can never drift apart.
|
|
82
|
-
* secret descriptor entry and is never written.
|
|
187
|
+
* unrecognized-key warning can never drift apart.
|
|
83
188
|
*/
|
|
84
189
|
export declare function serializeAuthBlock(settings: any): Record<string, any>;
|
|
85
190
|
/**
|
|
@@ -87,7 +192,7 @@ export declare function serializeAuthBlock(settings: any): Record<string, any>;
|
|
|
87
192
|
* direction). Kept as a focused helper over the shared descriptor; the full
|
|
88
193
|
* push path uses `parseTomlToAppSettings` for every section. Only keys present
|
|
89
194
|
* in the TOML are forwarded, so an omitted key never overwrites server state.
|
|
90
|
-
* Descriptor errors (a
|
|
195
|
+
* Descriptor errors (a type mismatch) are surfaced alongside the
|
|
91
196
|
* unrecognized-key warnings.
|
|
92
197
|
*/
|
|
93
198
|
export declare function parseAppAuthSettings(auth: Record<string, any>): ParsedAuthSettings;
|
|
@@ -96,7 +201,7 @@ type VarEntity = {
|
|
|
96
201
|
contentHash?: string;
|
|
97
202
|
};
|
|
98
203
|
/**
|
|
99
|
-
* Decide whether `
|
|
204
|
+
* Decide whether `config pull` should (over)write `vars.toml`, given the outcome
|
|
100
205
|
* of fetching the app's config vars.
|
|
101
206
|
*
|
|
102
207
|
* The distinction that matters (issue #1423 review): a SUCCESSFUL fetch that
|
|
@@ -120,23 +225,94 @@ export declare function planVarsPull(outcome: {
|
|
|
120
225
|
content: string | null;
|
|
121
226
|
varEntities: Record<string, VarEntity>;
|
|
122
227
|
};
|
|
228
|
+
/**
|
|
229
|
+
* `config pull --only var/<KEY>` — the same plan, narrowed to the named keys
|
|
230
|
+
* (issue #2645).
|
|
231
|
+
*
|
|
232
|
+
* `vars.toml` is one file for the whole var surface, so a scoped pull cannot
|
|
233
|
+
* just re-emit what the server returned: that would drop every var the
|
|
234
|
+
* operator did not name, including local edits they have not pushed yet. The
|
|
235
|
+
* merge is therefore local-file-first — the file's current keys are kept as
|
|
236
|
+
* they are, and only the SELECTED keys are taken from the server (or removed,
|
|
237
|
+
* when the server no longer has them).
|
|
238
|
+
*
|
|
239
|
+
* Pure like `planVarsPull`, for the same reason: this is the one place a pull
|
|
240
|
+
* can silently discard an author's work, so it has to be testable without a
|
|
241
|
+
* live server.
|
|
242
|
+
*/
|
|
243
|
+
export declare function planScopedVarsPull(serverVars: Array<{
|
|
244
|
+
key: string;
|
|
245
|
+
value?: string;
|
|
246
|
+
updatedAt?: string;
|
|
247
|
+
modifiedAt?: string;
|
|
248
|
+
}>, selectedKeys: ReadonlySet<string>, localVars: Record<string, string>, priorVars?: Record<string, VarEntity>): {
|
|
249
|
+
content: string;
|
|
250
|
+
varEntities: Record<string, VarEntity>;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Validate a `config push`'s webhook writes against the server's per-app cap.
|
|
254
|
+
* Returns a list of human-readable errors (empty when the push may proceed).
|
|
255
|
+
*
|
|
256
|
+
* Run in `config push`'s up-front preflight (the `planVarsPush` cap pattern) so a
|
|
257
|
+
* push that would run past the cap aborts BEFORE any mutation. Without it the
|
|
258
|
+
* push creates webhooks one at a time until the server rejects one at the cap,
|
|
259
|
+
* leaving a partial push.
|
|
260
|
+
*
|
|
261
|
+
* **Only creates are capped.** The server grandfathers apps that already hold
|
|
262
|
+
* more webhooks than the cap: it refuses new creates and nothing else. A plain
|
|
263
|
+
* file count would break that — `config pull` writes one toml per webhook, so a
|
|
264
|
+
* grandfathered app pulls more files than the cap and could then never push
|
|
265
|
+
* again, its workflows, databases and vars included. So the check compares the
|
|
266
|
+
* local keys against the keys already on the server: a push that creates
|
|
267
|
+
* nothing new is always allowed, however far over the cap the app is, and a
|
|
268
|
+
* push that does create is rejected only when the resulting row count would
|
|
269
|
+
* exceed the cap.
|
|
270
|
+
*
|
|
271
|
+
* The remote key list this runs against comes from `GET
|
|
272
|
+
* /admin/api/apps/{appId}/webhooks`, which applies no default status filter, so
|
|
273
|
+
* archived rows are in `remoteKeys` exactly as they are in the server's count.
|
|
274
|
+
* The two therefore agree on what is already on the server.
|
|
275
|
+
*
|
|
276
|
+
* Still advisory rather than authoritative: this counts creates the way the
|
|
277
|
+
* *config dir* implies, while the apply loop decides create-vs-update from
|
|
278
|
+
* local sync state, so a checkout with missing sync state issues creates this
|
|
279
|
+
* check did not predict. Those creates name keys the server already holds, and
|
|
280
|
+
* the server does not cap a create that adds no row — it answers 409
|
|
281
|
+
* `WEBHOOK_KEY_EXISTS`, which `adoptByKeyOnCreate409` converges on. A push
|
|
282
|
+
* allowed here can still be rejected server-side (concurrent writes, a stale
|
|
283
|
+
* list); that rejection carries `WEBHOOK_LIMIT_REACHED` and names the limit.
|
|
284
|
+
*
|
|
285
|
+
* Planned prunes are deliberately NOT netted out of the count: `--prune` runs
|
|
286
|
+
* after the webhook create loop, so a push that only fits once its prunes land
|
|
287
|
+
* would still fail mid-apply. Prune first, then push the additions.
|
|
288
|
+
*/
|
|
289
|
+
export declare function validateWebhookCapForPush(localKeys: string[], remoteKeys: string[]): string[];
|
|
123
290
|
/**
|
|
124
291
|
* Validate a parsed `vars.toml` table against the same key/value constraints
|
|
125
292
|
* the server enforces (key format, string type, non-empty, size cap) plus the
|
|
126
293
|
* aggregate per-app var-count cap. Returns a list of human-readable errors
|
|
127
294
|
* (empty when valid).
|
|
128
295
|
*
|
|
129
|
-
* Run in `
|
|
296
|
+
* Run in `config push`'s up-front preflight pass (issue #1423 review) so an
|
|
130
297
|
* invalid entry — or a file that would exceed the server's `MAX_VARS_PER_APP`
|
|
131
298
|
* cap — aborts BEFORE any mutation is applied. Validating only individual
|
|
132
299
|
* entries let a 101-entry file create/update many vars before the server
|
|
133
300
|
* rejected a later create at the cap, leaving a partial push (issue #1423
|
|
134
301
|
* review pass 3). Pure and side-effect-free so it's unit-testable without a
|
|
135
302
|
* live server.
|
|
303
|
+
*
|
|
304
|
+
* `onlyKeys` narrows the check to the keys a `config push --only var/<key>` will
|
|
305
|
+
* actually write (issue #2645). A single-object push must not be aborted by an
|
|
306
|
+
* entry it is not going to touch, so the per-entry checks run over the selected
|
|
307
|
+
* keys and the file-wide count cap — a property of the whole file, not of these
|
|
308
|
+
* entries — is left to the plan-level check the caller makes against the final
|
|
309
|
+
* server count.
|
|
136
310
|
*/
|
|
137
|
-
export declare function validateVarsFile(parsedVars: Record<string, unknown
|
|
311
|
+
export declare function validateVarsFile(parsedVars: Record<string, unknown>, options?: {
|
|
312
|
+
onlyKeys?: ReadonlySet<string>;
|
|
313
|
+
}): string[];
|
|
138
314
|
/**
|
|
139
|
-
* Plan the config-var writes a `
|
|
315
|
+
* Plan the config-var writes a `config push` should make, detecting concurrent
|
|
140
316
|
* remote edits before overwriting them (issue #1423 review).
|
|
141
317
|
*
|
|
142
318
|
* This is the client-side half of a two-layer concurrency guard. The vars
|
|
@@ -156,7 +332,7 @@ export declare function validateVarsFile(parsedVars: Record<string, unknown>): s
|
|
|
156
332
|
* removed locally whose remote value no longer matches the baseline was edited
|
|
157
333
|
* remotely since the last sync, and deleting it would silently discard that
|
|
158
334
|
* edit — reported as a conflict instead. `force` skips the conflict guard,
|
|
159
|
-
* matching `
|
|
335
|
+
* matching `config push --force` for every other entity.
|
|
160
336
|
*
|
|
161
337
|
* Pure and side-effect-free (the caller owns the API calls and sync-state
|
|
162
338
|
* updates) so the guard is unit-testable without a live server. Assumes
|
|
@@ -211,11 +387,11 @@ export declare function countVarsAfterPush(remoteVars: Array<{
|
|
|
211
387
|
deletions: string[];
|
|
212
388
|
}): number;
|
|
213
389
|
/**
|
|
214
|
-
* Compute the `
|
|
215
|
-
* this, `
|
|
390
|
+
* Compute the `config diff` rows for config vars (issue #1423 review). Before
|
|
391
|
+
* this, `config diff` ignored vars entirely, so an add/remove/value-drift between
|
|
216
392
|
* the local `vars.toml` and the server read as no difference. Value-aware: a
|
|
217
393
|
* var present on both sides whose value differs is reported as `modified`
|
|
218
|
-
* (framed like the other content-aware entities — `
|
|
394
|
+
* (framed like the other content-aware entities — `config pull` would rewrite the
|
|
219
395
|
* local value). Pure and side-effect-free so it's unit-testable without a live
|
|
220
396
|
* server.
|
|
221
397
|
*/
|
|
@@ -225,10 +401,386 @@ export declare function diffVars(localVars: Map<string, string>, remoteVars: Map
|
|
|
225
401
|
status: string;
|
|
226
402
|
hint?: string;
|
|
227
403
|
}>;
|
|
228
|
-
|
|
404
|
+
/**
|
|
405
|
+
* Which of a workflow's configs `config pull` writes as sidecar files
|
|
406
|
+
* (issue #2645, spec §Contracts/Named configs).
|
|
407
|
+
*
|
|
408
|
+
* Every config EXCEPT the active one. The active config's body is already the
|
|
409
|
+
* `steps` at the top of `workflows/<key>.toml` — that is where authors edit it
|
|
410
|
+
* and where `applyWorkflowBody` writes it — so emitting it again as a sidecar
|
|
411
|
+
* would put the same body in two files and create a mismatch to adjudicate on
|
|
412
|
+
* the next push. Two declarations of one piece of state is the shape of problem
|
|
413
|
+
* this whole issue removes; it would be perverse to introduce one here.
|
|
414
|
+
*
|
|
415
|
+
* A config whose steps were not fetched is SKIPPED rather than written empty.
|
|
416
|
+
* A sidecar is a claim about the server, and `steps = []` is a claim that would
|
|
417
|
+
* tell the next push to blank a live configuration.
|
|
418
|
+
*
|
|
419
|
+
* Pure, so the selection is unit-testable without a live server.
|
|
420
|
+
*/
|
|
421
|
+
export declare function workflowConfigSidecarsToWrite(workflow: any, configs: any[]): Array<{
|
|
422
|
+
name: string;
|
|
423
|
+
content: string;
|
|
424
|
+
}>;
|
|
425
|
+
/**
|
|
426
|
+
* The names a workflow's configs occupy on the server, whatever their body.
|
|
427
|
+
*
|
|
428
|
+
* `workflowConfigSidecarsToWrite` deliberately skips a config whose steps were
|
|
429
|
+
* not fetched, so it cannot answer "which sidecar files are stale". This can:
|
|
430
|
+
* a local file whose name is absent HERE names a config the server no longer
|
|
431
|
+
* has, which is the only safe basis for deleting it (issue #2645, review
|
|
432
|
+
* follow-up).
|
|
433
|
+
*/
|
|
434
|
+
export declare function workflowConfigNamesOnServer(workflow: any, configs: any[]): Set<string>;
|
|
435
|
+
/**
|
|
436
|
+
* Delete the sidecars naming a config the server no longer has (#2645).
|
|
437
|
+
*
|
|
438
|
+
* Pull is what makes the repo describe the server, and a sidecar left behind
|
|
439
|
+
* after its config was archived — or after that config became the active one,
|
|
440
|
+
* whose body is the workflow file's `steps` — is a file that would RE-CREATE it
|
|
441
|
+
* on the next push. The repo would then be silently undoing the archive.
|
|
442
|
+
*
|
|
443
|
+
* Returns the file names removed, so pull can say what it did.
|
|
444
|
+
*/
|
|
445
|
+
export declare function removeStaleWorkflowConfigSidecars(configDir: string, key: string, namesOnServer: Set<string>): string[];
|
|
446
|
+
/**
|
|
447
|
+
* The content hash `config push` skips a workflow on — sidecars included.
|
|
448
|
+
*
|
|
449
|
+
* The stored hash used to cover `workflows/<key>.toml` alone, so editing or
|
|
450
|
+
* adding a named config sidecar without touching the workflow file left the
|
|
451
|
+
* hash unchanged: push reported success and made no call at all, which is
|
|
452
|
+
* worse than failing (issue #2645, review follow-up). The named configs are
|
|
453
|
+
* part of what push applies, so they are part of what push compares.
|
|
454
|
+
*
|
|
455
|
+
* A workflow with no sidecars hashes exactly as before, so the common case
|
|
456
|
+
* does not re-push once on upgrade. An unreadable sidecar hashes to a distinct
|
|
457
|
+
* value rather than throwing: a hash is a comparison, and the parse error
|
|
458
|
+
* belongs to the apply path, which raises it with the file name attached.
|
|
459
|
+
*/
|
|
460
|
+
export declare function workflowHashInput(configDir: string, key: string, parsedWorkflowToml: any): any;
|
|
461
|
+
/** See `workflowHashInput`. */
|
|
462
|
+
export declare function computeWorkflowContentHash(configDir: string, key: string, parsedWorkflowToml: any): string;
|
|
463
|
+
/**
|
|
464
|
+
* Canonical hash of a named config sidecar, for `config diff` (#2645).
|
|
465
|
+
*
|
|
466
|
+
* Hashes the parsed body rather than the bytes so comment and key-order
|
|
467
|
+
* differences do not read as drift — the same semantic comparison `config diff`
|
|
468
|
+
* makes everywhere else (spec criterion 3).
|
|
469
|
+
*/
|
|
470
|
+
export declare function hashWorkflowConfigSidecar(parsed: any): string;
|
|
471
|
+
/**
|
|
472
|
+
* Does the file's `[workflow].activeConfigName` name a configuration that the
|
|
473
|
+
* push could actually activate (#2743, review follow-up)?
|
|
474
|
+
*
|
|
475
|
+
* The diff normalizer equates an omitted `activeConfigName` with `default` —
|
|
476
|
+
* the name the server itself gives the config it creates — so a file that
|
|
477
|
+
* spells `default` out hashes equal to the omitted form. For a workflow with no
|
|
478
|
+
* configuration at all (a legacy row the GET's auto-migration could not heal),
|
|
479
|
+
* the remote side serializes an EMPTY active config name and normalizes to
|
|
480
|
+
* `default` too, so the pair hashes equal while `config push` would stop on
|
|
481
|
+
* `activeConfigName "default" names no configuration`. Reporting Synced for a
|
|
482
|
+
* file whose next push fails is the one drift a change gate must not skip.
|
|
483
|
+
*
|
|
484
|
+
* A name the repo carries as a sidecar resolves: push creates that config and
|
|
485
|
+
* activates it (and the sidecar comparison already reports the difference).
|
|
486
|
+
* Read-only and non-throwing, like every other diff predicate.
|
|
487
|
+
*/
|
|
488
|
+
export declare function workflowActiveConfigNameUnresolvable(configDir: string, key: string, localParsed: any, configs: any[]): boolean;
|
|
489
|
+
/**
|
|
490
|
+
* The `config diff` note for a workflow whose file names no configuration while
|
|
491
|
+
* the server runs one other than the `default` it creates itself (#2743, review
|
|
492
|
+
* follow-up).
|
|
493
|
+
*
|
|
494
|
+
* Such a file is not drift push can act on: activation is preserved, not
|
|
495
|
+
* converged, so the row is Synced — the alternative, reporting Modified,
|
|
496
|
+
* repeats forever because no push changes it. But the repo does not describe
|
|
497
|
+
* which configuration is live, and only `config pull` records it, so the fact is
|
|
498
|
+
* carried as a hint on the Synced row — the same way an operational disable is
|
|
499
|
+
* (`outOfServiceHint`). The server-created `default` is the shape a
|
|
500
|
+
* hand-authored file already means, so it is silent.
|
|
501
|
+
*
|
|
502
|
+
* Pure and non-throwing, like every other diff predicate. Returns the existing
|
|
503
|
+
* hint untouched when there is nothing to add.
|
|
504
|
+
*/
|
|
505
|
+
export declare function unmanagedActiveConfigHint(localParsed: any, workflow: any, configs: any[], existingHint?: string): string | undefined;
|
|
506
|
+
/**
|
|
507
|
+
* Drop the archived rows from a pull listing (#2803).
|
|
508
|
+
*
|
|
509
|
+
* A tombstone is retired, not configuration. Exporting one wrote a file the
|
|
510
|
+
* repo then claimed to describe — and, once `status` left the TOML surface, a
|
|
511
|
+
* file whose very next push would fail on the `status` line it carried.
|
|
512
|
+
* `config push --prune` is the path that reclaims an archived row; pull's job
|
|
513
|
+
* is to describe what is configured.
|
|
514
|
+
*/
|
|
515
|
+
export declare function skipArchivedForPull<T extends {
|
|
516
|
+
status?: unknown;
|
|
517
|
+
}>(items: T[]): T[];
|
|
518
|
+
/**
|
|
519
|
+
* The sync-state entries a pull keeps for the archived rows it did NOT export
|
|
520
|
+
* (#2803).
|
|
521
|
+
*
|
|
522
|
+
* Pull removes a tombstone's file, and prune candidates come only from prior
|
|
523
|
+
* sync state — so dropping the state entry with the file would strand the row:
|
|
524
|
+
* no file to delete, no managed key to prune, and (for webhooks and cron
|
|
525
|
+
* triggers) a cap slot and a reserved key nothing could ever reclaim, which is
|
|
526
|
+
* the exact failure #2232 fixed. So the pull records the tombstone instead: no
|
|
527
|
+
* file, but a managed entry carrying the archived row's CURRENT `modifiedAt`,
|
|
528
|
+
* so the next opt-in, confirmed `config push --prune` point-reads it, finds it
|
|
529
|
+
* unchanged, and hard-deletes it — the reclamation path the model promises.
|
|
530
|
+
*
|
|
531
|
+
* Only keys the config tree already managed are recorded. A row archived
|
|
532
|
+
* server-side that this repo never described is not this repo's to delete.
|
|
533
|
+
*
|
|
534
|
+
* Pure, so it is unit-testable without a live server.
|
|
535
|
+
*/
|
|
536
|
+
export declare function archivedTombstoneEntries(items: any[], select: {
|
|
537
|
+
keyOf: (item: any) => string;
|
|
538
|
+
idOf: (item: any) => string;
|
|
539
|
+
}, managedKeys: Iterable<string>): Record<string, {
|
|
540
|
+
id: string;
|
|
541
|
+
modifiedAt: string;
|
|
542
|
+
}>;
|
|
543
|
+
/**
|
|
544
|
+
* Fold one type's tombstone entries into the sync state this pull is building
|
|
545
|
+
* (#2803, corrected by #2887).
|
|
546
|
+
*
|
|
547
|
+
* Two rules, and the second one is the fix. A tombstone is recorded only for a
|
|
548
|
+
* key this pull did NOT export (an exported key is a live row and its own
|
|
549
|
+
* record) and only when the selection covers it. And a recorded key is marked
|
|
550
|
+
* as MATCHED: archived rows are dropped before `selectPull` runs, so the
|
|
551
|
+
* selector that named one never reached the set that decides whether the pull
|
|
552
|
+
* found what it was asked for. `config pull --only workflow/<key>` against an
|
|
553
|
+
* archived managed row therefore removed the file, saved the tombstone, and
|
|
554
|
+
* then exited 1 saying "the server has no workflow/<key>" — a failure report
|
|
555
|
+
* about a mutation that had already succeeded, which is the worst thing an
|
|
556
|
+
* automation can be told. The fix is generic, so integrations and webhooks
|
|
557
|
+
* (which had the bug first) get it too.
|
|
558
|
+
*
|
|
559
|
+
* Mutates `built` and `matchedSelectors` and returns the keys it recorded, so
|
|
560
|
+
* the caller can report each one. Pure otherwise, and unit-testable.
|
|
561
|
+
*/
|
|
562
|
+
export declare function recordArchivedTombstones(built: Record<string, {
|
|
563
|
+
id: string;
|
|
564
|
+
modifiedAt: string;
|
|
565
|
+
}>, tombstones: Record<string, {
|
|
566
|
+
id: string;
|
|
567
|
+
modifiedAt: string;
|
|
568
|
+
}>, context: {
|
|
569
|
+
label: string;
|
|
570
|
+
selection: SyncSelection | null;
|
|
571
|
+
matchedSelectors: Set<string>;
|
|
572
|
+
}): string[];
|
|
573
|
+
/**
|
|
574
|
+
* The `config diff` annotation for an object an operator took out of service
|
|
575
|
+
* (issue #2645, criterion 9; #2803).
|
|
576
|
+
*
|
|
577
|
+
* Availability is deliberately not a TOML key, so an object that is out of
|
|
578
|
+
* service is NOT drift — the committed configuration still describes it
|
|
579
|
+
* exactly. Saying so out loud is the point: the reason the control exists is
|
|
580
|
+
* that an out-of-band write used to make `config diff` report a difference
|
|
581
|
+
* nobody intentionally created (#1976). Reporting it as a hint on an
|
|
582
|
+
* otherwise-`Synced` row is what keeps that promise visible instead of merely
|
|
583
|
+
* true.
|
|
584
|
+
*
|
|
585
|
+
* Reads either spelling: the canonical `status: "inactive"` for a type that has
|
|
586
|
+
* cut over to #2803's single control, and the superseded
|
|
587
|
+
* `operationallyDisabled` boolean for one that has not yet.
|
|
588
|
+
*
|
|
589
|
+
* An ARCHIVED row gets its own annotation. `config pull` no longer exports
|
|
590
|
+
* tombstones, so without one an archived webhook or integration whose file is
|
|
591
|
+
* still in the tree read as plain `Synced` — the one status where "the server
|
|
592
|
+
* has exactly what the file says" is most misleading.
|
|
593
|
+
*
|
|
594
|
+
* When the row already carries a hint — a real content difference — the toggle
|
|
595
|
+
* is APPENDED rather than substituted: the drift is the part that needs acting
|
|
596
|
+
* on, and dropping it to announce the pause would hide the more urgent fact.
|
|
597
|
+
*
|
|
598
|
+
* Pure, so it is unit-testable without a live server.
|
|
599
|
+
*/
|
|
600
|
+
export declare function outOfServiceHint(remote: any, existingHint?: string): string | undefined;
|
|
601
|
+
/**
|
|
602
|
+
* Serialize an integration DETAIL response to its `integrations/<key>.toml`.
|
|
603
|
+
*
|
|
604
|
+
* #2631: `accessRule` is the CEL expression gating who may call the
|
|
605
|
+
* integration. It comes back on the detail only (not the list summary), and it
|
|
606
|
+
* is written to TOML so `config pull` → `config push` round-trips the rule instead
|
|
607
|
+
* of clearing it. A null/empty rule has no TOML representation and is simply
|
|
608
|
+
* omitted; the push path sends `null` for an omitted rule, which matches the
|
|
609
|
+
* server state it came from.
|
|
610
|
+
*/
|
|
611
|
+
export declare function serializeIntegration(integration: any, logger?: (message: string) => void): string;
|
|
612
|
+
/**
|
|
613
|
+
* Build the `config push` body for one integration TOML.
|
|
614
|
+
*
|
|
615
|
+
* `accessRule` follows the #1567 TOML-owned rule: send value-or-`null` so
|
|
616
|
+
* removing the line from the file clears the rule server-side rather than
|
|
617
|
+
* leaving a stale one live. There is deliberately NO local "rule is required"
|
|
618
|
+
* check here — a remote integration missing from local sync state is pushed as
|
|
619
|
+
* a create, expected to 409, and adopted by key (#1006); a local check would
|
|
620
|
+
* strand that path. The server decides (#2631).
|
|
621
|
+
*/
|
|
622
|
+
export declare function buildIntegrationPushPayload(key: string, tomlData: any, { mode }?: {
|
|
623
|
+
mode?: PushMode;
|
|
624
|
+
}): Record<string, any>;
|
|
625
|
+
export declare function webhookConfigToTomlTable(scheme: string, config: any): Record<string, any> | undefined;
|
|
626
|
+
export declare function tomlTableToWebhookConfig(scheme: string, table: any): Record<string, any> | undefined;
|
|
627
|
+
export declare function serializeWebhook(webhook: any, logger?: (message: string) => void): string;
|
|
628
|
+
/**
|
|
629
|
+
* Build the `config push` body for one webhook TOML.
|
|
630
|
+
*
|
|
631
|
+
* The `[webhook]`-derived half comes from the vendored definition; the four
|
|
632
|
+
* structural tables the file also carries (`[allowedIps]`, `[inputMapping]`,
|
|
633
|
+
* `[metadata]`, `[verification.<scheme>]`) keep their own conversion, which is
|
|
634
|
+
* why the definition declares them `structural` rather than as fields.
|
|
635
|
+
*/
|
|
636
|
+
export declare function buildWebhookPushPayload(key: string, tomlData: any, { mode }?: {
|
|
637
|
+
mode?: PushMode;
|
|
638
|
+
}): Record<string, any>;
|
|
639
|
+
export declare function serializeCronTrigger(trigger: any, logger?: (message: string) => void): string;
|
|
640
|
+
/**
|
|
641
|
+
* Build the `config push` body for one cron-trigger TOML.
|
|
642
|
+
*
|
|
643
|
+
* `state` is absent from the create body because the definition says the create
|
|
644
|
+
* handler assigns it (`writableOn: ["update"]`) — the CLI used to send it and
|
|
645
|
+
* the server used to drop it silently.
|
|
646
|
+
*/
|
|
647
|
+
export declare function buildCronTriggerPushPayload(key: string, tomlData: any, { mode }?: {
|
|
648
|
+
mode?: PushMode;
|
|
649
|
+
}): Record<string, any>;
|
|
650
|
+
export declare function serializeBlobBucket(bucket: any, logger?: (message: string) => void): string;
|
|
651
|
+
/**
|
|
652
|
+
* Build the PATCH payload for a blob-bucket update from local TOML.
|
|
653
|
+
*
|
|
654
|
+
* EXACT extraction of the inline builder shared by the existing-update and
|
|
655
|
+
* 409-adopt branches — do NOT "clean up" the truthiness checks on the access
|
|
656
|
+
* model. The server treats `preset` and `accessPolicy` as mutually exclusive,
|
|
657
|
+
* only clears `ruleSetId` when a preset/accessPolicy is also present, and
|
|
658
|
+
* refuses to leave a bucket with no access model or a blank name — so those
|
|
659
|
+
* fields are not clearable. `bucketKey` and `ttlTier` are immutable and never
|
|
660
|
+
* sent. Both call sites must go through this helper so their field sets never
|
|
661
|
+
* drift.
|
|
662
|
+
*/
|
|
663
|
+
export declare function buildBlobBucketUpdatePayload(bucket: any): any;
|
|
664
|
+
/**
|
|
665
|
+
* Which `[[configs]]` entry a prompt file marks as live (issue #2645).
|
|
666
|
+
*
|
|
667
|
+
* Exactly one entry may carry `active = true`. Two is an error rather than a
|
|
668
|
+
* first-wins rule: making the file's meaning depend on entry order is the kind
|
|
669
|
+
* of implicit behavior this issue exists to remove, and silently picking one
|
|
670
|
+
* would let the repo claim something the server does not do.
|
|
671
|
+
*
|
|
672
|
+
* `isActive` is accepted as a legacy spelling — the create leg read it before
|
|
673
|
+
* pull ever wrote a marker, so a hand-authored file carrying it keeps working.
|
|
674
|
+
*/
|
|
675
|
+
export declare function promptActiveConfigName(configs: any[]): string | undefined;
|
|
676
|
+
/**
|
|
677
|
+
* The push payload for one `[[configs]]` entry of a prompt (issue #2645).
|
|
678
|
+
*
|
|
679
|
+
* One builder for both directions so create and update cannot drift: a field
|
|
680
|
+
* the file owns has to reach the server whether the config is new or already
|
|
681
|
+
* there, which is what "removing a flag removes no capability" means in
|
|
682
|
+
* practice.
|
|
683
|
+
*
|
|
684
|
+
* `description` is sent as `?? null` on update and omitted on create (#1567):
|
|
685
|
+
* dropping the key from a `[[configs]]` block must CLEAR it on an existing
|
|
686
|
+
* config, and the PATCH nulls on an explicit `null` while preserving on omit.
|
|
687
|
+
* `status` is the opposite — omitted means "unchanged", because the server
|
|
688
|
+
* defaults a new config to active and an existing one keeps what it has.
|
|
689
|
+
*/
|
|
690
|
+
export declare function promptConfigPayload(tomlConfig: any, options?: {
|
|
691
|
+
create?: boolean;
|
|
692
|
+
}): Record<string, any>;
|
|
693
|
+
/**
|
|
694
|
+
* Build the POST body for a blob-bucket create from local TOML.
|
|
695
|
+
*
|
|
696
|
+
* `ttlTier` is here and absent from the update body because the definition says
|
|
697
|
+
* so (`writableOn: ["create"]`) — the retention tier is fixed at creation.
|
|
698
|
+
*/
|
|
699
|
+
export declare function buildBlobBucketCreatePayload(key: string, bucket: any): Record<string, any>;
|
|
700
|
+
export declare function serializePrompt(prompt: any, logger?: (message: string) => void): string;
|
|
701
|
+
/**
|
|
702
|
+
* The `config push` warning for a resource pushed with no `accessRule` (#2652).
|
|
703
|
+
*
|
|
704
|
+
* Returns `null` when a rule is declared — and for a `runAs = "system"`
|
|
705
|
+
* workflow, which rejects any rule and cannot be started by a member at all.
|
|
706
|
+
* Otherwise it names the resource, the consequence, and the one-line restore,
|
|
707
|
+
* because after the #2652 flip a ruleless prompt or caller workflow denies
|
|
708
|
+
* every non-admin caller from the deploy onward.
|
|
709
|
+
*
|
|
710
|
+
* Both kinds now push `accessRule` as value-or-`null` — the prompt since #1567,
|
|
711
|
+
* the workflow since #2743's always-send conversion — so for either one an
|
|
712
|
+
* omitted key really does leave the resource ruleless on the server. The
|
|
713
|
+
* workflow wording says so explicitly, because it used to say the opposite
|
|
714
|
+
* (the update path preserved a stored rule) and an operator who learned that
|
|
715
|
+
* caveat needs to see it retracted.
|
|
716
|
+
*/
|
|
717
|
+
export declare function missingAccessRuleWarning(opts: {
|
|
718
|
+
kind: "prompt" | "workflow";
|
|
719
|
+
key: string;
|
|
720
|
+
accessRule: unknown;
|
|
721
|
+
runAs?: unknown;
|
|
722
|
+
}): string | null;
|
|
723
|
+
/**
|
|
724
|
+
* Build the `config push` body for one prompt TOML's `[prompt]` table.
|
|
725
|
+
*
|
|
726
|
+
* The prompt's own fields only — its `[[configs]]` entries are separate
|
|
727
|
+
* requests, built by `buildPromptConfigPayload`.
|
|
728
|
+
*/
|
|
729
|
+
export declare function buildPromptPushPayload(key: string, tomlData: any, { mode }?: {
|
|
730
|
+
mode?: PushMode;
|
|
731
|
+
}): Record<string, any>;
|
|
732
|
+
/**
|
|
733
|
+
* Build the POST body for a prompt create.
|
|
734
|
+
*
|
|
735
|
+
* `POST /prompts` is the one endpoint that writes BOTH models: the prompt, and
|
|
736
|
+
* the "default" `AppPromptConfig` it seeds from the same body. So this body is
|
|
737
|
+
* the `[prompt]` table's create half plus the FIRST `[[configs]]` entry's LLM
|
|
738
|
+
* settings — minus the three keys the prompt half owns or the handler assigns
|
|
739
|
+
* itself:
|
|
740
|
+
*
|
|
741
|
+
* - `configName` — the seeded config is always named "default".
|
|
742
|
+
* - `description` — the body's slot belongs to the PROMPT, and the handler
|
|
743
|
+
* hard-codes the seeded config's to "Default configuration". An authored
|
|
744
|
+
* `[[configs]]` description therefore has nowhere to go in this body, so
|
|
745
|
+
* push applies it right after the create rather than losing it.
|
|
746
|
+
* - `outputSchema` — same shape: the body's slot is the prompt's, so the
|
|
747
|
+
* seeded config's own value is applied by the follow-up PATCH
|
|
748
|
+
* (`seededOnlyPromptConfigFields`) instead of being dropped.
|
|
749
|
+
*
|
|
750
|
+
* Every other config field rides along from the definition, so a new LLM
|
|
751
|
+
* setting reaches a brand-new prompt without an edit at this call site.
|
|
752
|
+
*/
|
|
753
|
+
export declare function buildPromptCreatePayload(key: string, tomlData: any): Record<string, any>;
|
|
754
|
+
/**
|
|
755
|
+
* The PATCH body reconciling the seeded config with the first `[[configs]]`
|
|
756
|
+
* entry, or `null` when the entry needs none of it.
|
|
757
|
+
*
|
|
758
|
+
* #2972 — the entry's NAME belongs in this patch too. The create handler names
|
|
759
|
+
* the seeded config "default" whatever the entry is called, so a file whose
|
|
760
|
+
* first entry is named anything else got that entry's settings stored under
|
|
761
|
+
* "default": a later entry actually named "default" then collided with it and
|
|
762
|
+
* the push died half-applied, and a file naming no entry "default" quietly
|
|
763
|
+
* landed a config it never mentions while its first entry never existed. Both
|
|
764
|
+
* end here, because the rename runs before any further config is created.
|
|
765
|
+
*
|
|
766
|
+
* `seededConfigName` is what the server called it — read from the create
|
|
767
|
+
* response rather than assumed, so a handler that one day honors the name
|
|
768
|
+
* produces no needless PATCH.
|
|
769
|
+
*/
|
|
770
|
+
export declare function seededOnlyPromptConfigFields(firstConfig: any, seededConfigName?: string): Record<string, any> | null;
|
|
771
|
+
/**
|
|
772
|
+
* Build the create/update body for ONE `[[configs]]` entry.
|
|
773
|
+
*
|
|
774
|
+
* `create` carries the provider/model defaults a brand-new config needs; on
|
|
775
|
+
* update those defaults must NOT be applied, or editing an unrelated key would
|
|
776
|
+
* silently re-point a config at the default model.
|
|
777
|
+
*/
|
|
778
|
+
export declare function buildPromptConfigPayload(tomlConfig: any, { mode }?: {
|
|
779
|
+
mode?: PushMode;
|
|
780
|
+
}): Record<string, any>;
|
|
229
781
|
/**
|
|
230
782
|
* Rewrite a workflow TOML file's string-form `inputSchema` / `outputSchema` to
|
|
231
|
-
* native object form for `
|
|
783
|
+
* native object form for `config migrate-toml` (issue #1446).
|
|
232
784
|
*
|
|
233
785
|
* Purely local, no server fetch. Parses the raw source WITHOUT expanding
|
|
234
786
|
* fragment `include`s (so those directives survive the rewrite), swaps a
|
|
@@ -245,19 +797,236 @@ export declare function rewriteWorkflowSchemasToNative(rawToml: string, workflow
|
|
|
245
797
|
content: string;
|
|
246
798
|
changed: boolean;
|
|
247
799
|
};
|
|
800
|
+
/**
|
|
801
|
+
* #2644 — `config push` rejects any key the vendored definition does not
|
|
802
|
+
* recognize, so a mistyped or newer-server key fails locally instead of being
|
|
803
|
+
* silently dropped on the way to the server (design gate, 2026-08-12). Returns
|
|
804
|
+
* the error message, or null when every key is known.
|
|
805
|
+
*
|
|
806
|
+
* Works for any migrated type, single or repeated: a repeated table's entries
|
|
807
|
+
* are checked one by one and the unknown keys reported together, so a typo in
|
|
808
|
+
* the third `[[configs]]` block is as loud as one in the first.
|
|
809
|
+
*/
|
|
810
|
+
export declare function configUnknownKeyError(filePath: string, tomlData: any, table: ConfigTable): string | null;
|
|
811
|
+
/**
|
|
812
|
+
* #2644 — the document-level half of the same rejection: a top-level table the
|
|
813
|
+
* object's definition does not declare, or a field table written with the wrong
|
|
814
|
+
* array markers. Returns one message per problem, or `[]` when the file's shape
|
|
815
|
+
* is recognized.
|
|
816
|
+
*
|
|
817
|
+
* Unrecognized keys INSIDE a table were already rejected; a mistyped table
|
|
818
|
+
* HEADER was not, and it is the more destructive of the two — `[integraton]`
|
|
819
|
+
* leaves `[integration]` absent, so the push builder reads an empty table and
|
|
820
|
+
* sends `description: null` / `accessRule: null`, CLEARING them server-side.
|
|
821
|
+
* The design gate settled the posture (2026-08-12): push rejects everything
|
|
822
|
+
* unknown, tables included.
|
|
823
|
+
*/
|
|
824
|
+
export declare function configDocumentShapeErrors(filePath: string, tomlData: any, surface: ConfigObjectSurface): string[];
|
|
825
|
+
/**
|
|
826
|
+
* Which directories `config push` checks for unrecognized keys, and against which
|
|
827
|
+
* tables.
|
|
828
|
+
*
|
|
829
|
+
* Derived from the registry crossed with `SYNC_RESOURCE_TYPES`, so a type
|
|
830
|
+
* gains the check by being defined — nobody has to remember to list it here.
|
|
831
|
+
*
|
|
832
|
+
* Workflows carry an EMPTY `tables` list: `validateWorkflowToml` already
|
|
833
|
+
* reports their `[workflow]` keys alongside the #685 misnest check, in one
|
|
834
|
+
* message per file, but nothing checked a workflow file's top-level tables —
|
|
835
|
+
* so the surface is listed here for the document-level check and skipped for
|
|
836
|
+
* the per-table one.
|
|
837
|
+
*/
|
|
838
|
+
export declare function unknownKeyPreflightTargets(): Array<{
|
|
839
|
+
dir: string;
|
|
840
|
+
surface: ConfigObjectSurface;
|
|
841
|
+
tables: ConfigTable[];
|
|
842
|
+
}>;
|
|
843
|
+
/** One config file the TOML preflight rejects, and the row it belongs to. */
|
|
844
|
+
export interface ConfigFileValidationError {
|
|
845
|
+
/** The `config diff` row type, e.g. `prompt`. */
|
|
846
|
+
type: string;
|
|
847
|
+
/** The row key the local file pairs on. */
|
|
848
|
+
key: string;
|
|
849
|
+
filePath: string;
|
|
850
|
+
/** The messages, verbatim — the same text `config push` aborts with. */
|
|
851
|
+
messages: string[];
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Every per-entity config file whose TOML `config push` would refuse (#2880
|
|
855
|
+
* criterion 1, symmetry).
|
|
856
|
+
*
|
|
857
|
+
* One collector, two commands. Push aborts on the messages before issuing a
|
|
858
|
+
* request; `config diff` renders them as their own rows, because the failure
|
|
859
|
+
* this closes is a file diff calls Synced and push then rejects — the state the
|
|
860
|
+
* issue's reporter hit after #2803 retired `[prompt].status`, where a local key
|
|
861
|
+
* with no server counterpart is invisible to a comparison and fatal to a push.
|
|
862
|
+
*
|
|
863
|
+
* Three checks, all definition-driven: the document's table shape, keys no
|
|
864
|
+
* definition declares (retired keys among them), and — since #2880 — values
|
|
865
|
+
* whose spelling is not the declared type. A file that does not parse is left
|
|
866
|
+
* to the per-type loop that reads it, which names the parse error itself.
|
|
867
|
+
*/
|
|
868
|
+
export declare function collectConfigFileValidationErrors(configDir: string): ConfigFileValidationError[];
|
|
869
|
+
/**
|
|
870
|
+
* Every `<key>.tests/*.toml` sidecar under a config directory.
|
|
871
|
+
*
|
|
872
|
+
* Test cases are a registered surface (#2644 phase 3) but they are not a
|
|
873
|
+
* `SYNC_RESOURCE_TYPES` directory — they live beside the block they test, in
|
|
874
|
+
* `prompts/<key>.tests/`, `workflows/<key>.tests/`, `transforms/<name>.tests/`
|
|
875
|
+
* and (since #2769) `integrations/<key>.tests/` — so
|
|
876
|
+
* `unknownKeyPreflightTargets`, which crosses
|
|
877
|
+
* the registry with that table, never reaches them. Listing them here gives the
|
|
878
|
+
* sidecars the same unrecognized-key rejection every per-entity file has
|
|
879
|
+
* (criterion 6): a mistyped `[test]` key fails the push instead of being
|
|
880
|
+
* dropped on the way to the server.
|
|
881
|
+
*/
|
|
882
|
+
export declare function testCaseTomlFiles(configDir: string): string[];
|
|
883
|
+
/** The `[workflow]` case of `configUnknownKeyError` (#2644 phase 1). */
|
|
884
|
+
export declare function workflowUnknownKeyError(filePath: string, tomlData: any): string | null;
|
|
885
|
+
/**
|
|
886
|
+
* Will `config push` reach this workflow key through its UPDATE path?
|
|
887
|
+
*
|
|
888
|
+
* The push takes that path when sync state already holds an id for the key —
|
|
889
|
+
* the `existingId` the apply loop reads. Everything else is a create (including
|
|
890
|
+
* a create that 409s and adopts by key, which cannot be known before the server
|
|
891
|
+
* answers). Exported so the front-loaded preflight can decide, before the first
|
|
892
|
+
* mutating call, which files the update-only `name` pre-check applies to.
|
|
893
|
+
*/
|
|
894
|
+
export declare function isWorkflowUpdateTarget(key: string, syncState: {
|
|
895
|
+
entities?: {
|
|
896
|
+
workflows?: Record<string, any>;
|
|
897
|
+
};
|
|
898
|
+
} | null): boolean;
|
|
899
|
+
/**
|
|
900
|
+
* Will the apply loop actually SEND this workflow, or skip it as unchanged?
|
|
901
|
+
*
|
|
902
|
+
* Mirrors the loop's skip condition (`!force && existingId &&
|
|
903
|
+
* !shouldPushExpandedFile(...)`): a create always sends, `--force` always
|
|
904
|
+
* sends, and an update sends only when the expanded content (workflow file plus
|
|
905
|
+
* its config sidecars) differs from the hash sync state recorded for it.
|
|
906
|
+
*
|
|
907
|
+
* Exported so the front-loaded preflight can scope the update-only `name`
|
|
908
|
+
* pre-check to the files this push will really update. Without that scoping a
|
|
909
|
+
* workflow created through the manifest-key `name` fallback — an authoring
|
|
910
|
+
* shape the create path deliberately supports — would push once and then fail
|
|
911
|
+
* every later push, including a no-change one and its `--dry-run`, on a file
|
|
912
|
+
* the push was never going to send (#2743, review follow-up).
|
|
913
|
+
*/
|
|
914
|
+
export declare function workflowPushSendsUpdate(configDir: string, key: string, parsedWorkflowToml: any, syncState: {
|
|
915
|
+
entities?: {
|
|
916
|
+
workflows?: Record<string, any>;
|
|
917
|
+
};
|
|
918
|
+
} | null, force?: boolean): boolean;
|
|
919
|
+
/**
|
|
920
|
+
* The UPDATE path's `name` pre-check (#2743).
|
|
921
|
+
*
|
|
922
|
+
* Always-send means the update payload carries `name: value ?? null`, and the
|
|
923
|
+
* server rejects a `null` name — it is required and cannot be cleared. Caught
|
|
924
|
+
* here, the operator gets an error naming their file before any PATCH is sent;
|
|
925
|
+
* caught server-side, they get a bare 400 part-way through a push. Pull always
|
|
926
|
+
* writes `name`, so a file reaching the update path without one is a deliberate
|
|
927
|
+
* authoring state, not a round-trip artifact.
|
|
928
|
+
*
|
|
929
|
+
* Called from the front-loaded preflight (#976) for every workflow this push
|
|
930
|
+
* will SEND through the update path (`workflowPushSendsUpdate` — an unchanged
|
|
931
|
+
* file the apply loop skips is not one) — so it aborts before the first
|
|
932
|
+
* mutating call and reports under `--dry-run` too — and again inside the update
|
|
933
|
+
* closure as defense-in-depth, which is also where the adopt-by-key path (a
|
|
934
|
+
* create the server 409s, unknowable up front) meets it.
|
|
935
|
+
*
|
|
936
|
+
* The CREATE path deliberately does NOT call this: it falls back to the
|
|
937
|
+
* manifest key (`name: workflow.name || key`), which is the accepted way to
|
|
938
|
+
* author a new workflow whose display name is its key.
|
|
939
|
+
*
|
|
940
|
+
* Returns `null` when the name is present.
|
|
941
|
+
*/
|
|
942
|
+
export declare function workflowUpdateNameError(filePath: string, workflow: any): string | null;
|
|
248
943
|
export declare function serializeWorkflow(workflow: any, draft: any, configs: any[], logger?: (message: string) => void): string;
|
|
249
|
-
export declare function
|
|
250
|
-
|
|
944
|
+
export declare function serializeDatabaseType(typeConfig: any, operations: any[], ruleSetIdToName: Map<string, string>, options?: {
|
|
945
|
+
/** Per-op form hints derived from the existing file. */
|
|
946
|
+
hints?: OperationFormHints;
|
|
947
|
+
/** Default form for ops with no hint. New files → "native". */
|
|
948
|
+
defaultForm?: FieldForm;
|
|
949
|
+
/** Sink for human-readable fallback messages (logged via `info`). */
|
|
950
|
+
logger?: (message: string) => void;
|
|
951
|
+
/** Server subscription rows to emit as `[[subscriptions]]` (issue #803). */
|
|
952
|
+
subscriptions?: any[];
|
|
953
|
+
}): string;
|
|
251
954
|
/**
|
|
252
|
-
*
|
|
955
|
+
* Flatten an email-template detail response onto its field surface.
|
|
253
956
|
*
|
|
254
|
-
*
|
|
957
|
+
* The API returns `{ emailType, hasOverride, override: {…}, default: {…} }` —
|
|
958
|
+
* only the `override` half is the field surface (the `default` half is what the
|
|
959
|
+
* platform ships), which is why both envelope keys are declared
|
|
960
|
+
* `responseOnlyKeys` in the definition.
|
|
961
|
+
*/
|
|
962
|
+
export declare function emailTemplateOverrideRecord(template: any): Record<string, any>;
|
|
963
|
+
export declare function serializeEmailTemplate(template: any, logger?: (message: string) => void): string;
|
|
964
|
+
/**
|
|
965
|
+
* Build the upsert body for one email-template TOML.
|
|
966
|
+
*
|
|
967
|
+
* `emailType` is deliberately absent: it identifies WHICH built-in template the
|
|
968
|
+
* override replaces and travels in the URL path, which is what the definition's
|
|
969
|
+
* `notExposed`/`tomlOnlyKeys` pair records.
|
|
970
|
+
*/
|
|
971
|
+
export declare function buildEmailTemplatePushPayload(tomlData: any): {
|
|
972
|
+
subject: string;
|
|
973
|
+
htmlBody: string;
|
|
974
|
+
textBody: string;
|
|
975
|
+
};
|
|
976
|
+
export declare function serializeRuleSet(ruleSet: any, logger?: (message: string) => void): string;
|
|
977
|
+
/**
|
|
978
|
+
* The create/update body for one rule-set TOML (#2644).
|
|
979
|
+
*
|
|
980
|
+
* The field half comes from the definition — which is what keeps
|
|
981
|
+
* `resourceType` off the update body, since the update handler never reads it —
|
|
982
|
+
* and the structural `[rules]` tree is attached beside it.
|
|
983
|
+
*
|
|
984
|
+
* `name` is trimmed here because the server trims it before storing
|
|
985
|
+
* (`rule-sets-controller.ts`, create and update). This body is also what the
|
|
986
|
+
* comparator projects the local side through (#2731 B1), so leaving the
|
|
987
|
+
* untrimmed spelling in would compare a name the server will never hold: the
|
|
988
|
+
* push would "succeed", change nothing, and be planned again on every run.
|
|
989
|
+
*/
|
|
990
|
+
export declare function buildRuleSetPushPayload(tomlData: any, mode: PushMode): Record<string, any>;
|
|
991
|
+
export declare function serializeGroupTypeConfig(config: any, ruleSetIdToName: Map<string, string>, logger?: (message: string) => void): string;
|
|
992
|
+
/**
|
|
993
|
+
* The create/update body for one group-type config (#2644).
|
|
994
|
+
*
|
|
995
|
+
* Takes the entity `parseGroupTypeConfigToml` produced, AFTER the rule-set
|
|
996
|
+
* name → id resolution: the definition says which fields the server accepts in
|
|
997
|
+
* each mode, so `groupType` rides the create body and the URL on update.
|
|
998
|
+
*/
|
|
999
|
+
export declare function buildGroupTypeConfigPayload(configData: any, mode: PushMode): Record<string, any>;
|
|
1000
|
+
export declare function serializeCollectionTypeConfig(config: any, ruleSetIdToName: Map<string, string>, logger?: (message: string) => void): string;
|
|
1001
|
+
/** The create/update body for one collection-type config (#2644). */
|
|
1002
|
+
export declare function buildCollectionTypeConfigPayload(configData: any, mode: PushMode): Record<string, any>;
|
|
1003
|
+
/**
|
|
1004
|
+
* The upsert body for one metadata-category config (#2644).
|
|
1005
|
+
*
|
|
1006
|
+
* One endpoint serves create and update, so every field is writable in both
|
|
1007
|
+
* modes and the body carries the whole definition-declared surface. The
|
|
1008
|
+
* identity pair rides the URL too (`PUT …/{resourceType}/{category}`); sending
|
|
1009
|
+
* it in the body as well is what the POST form has always taken.
|
|
1010
|
+
*/
|
|
1011
|
+
export declare function buildMetadataCategoryPayload(configData: any): Record<string, any>;
|
|
1012
|
+
/**
|
|
1013
|
+
* Issue #1567 — the TOML-owned fields of a database-type config, derived from
|
|
1014
|
+
* the definition (#2644 phase 3).
|
|
1015
|
+
*
|
|
1016
|
+
* These are fields the `database-type-configs/*.toml` file OWNS: the local file is the
|
|
255
1017
|
* source of truth, so removing one from the TOML must clear it server-side
|
|
256
|
-
* (config-as-code), not silently preserve the stale value.
|
|
257
|
-
*
|
|
258
|
-
*
|
|
1018
|
+
* (config-as-code), not silently preserve the stale value.
|
|
1019
|
+
*
|
|
1020
|
+
* The list used to be written out here, a second statement of the `[type]`
|
|
1021
|
+
* field surface that a new field would have had to be added to by hand. It is
|
|
1022
|
+
* now every exposed `[type]` scalar except the immutable identity, plus the two
|
|
1023
|
+
* sub-trees the definition classifies `structural` and the file nonetheless
|
|
1024
|
+
* owns whole (`triggers`, `metadataManifest`). `schema` is deliberately NOT
|
|
1025
|
+
* owned this way — it keeps its own `hasSchema` prior-state discriminator,
|
|
1026
|
+
* since a schema is a large sub-tree whose absence is ambiguous.
|
|
259
1027
|
*/
|
|
260
|
-
export declare
|
|
1028
|
+
export declare function dbTypeOwnedScalars(table?: ConfigTable): string[];
|
|
1029
|
+
export declare const DB_TYPE_OWNED_SCALARS: readonly string[];
|
|
261
1030
|
/** The user-facing TOML key for a wire field (for push output). */
|
|
262
1031
|
export declare function dbTypeFieldLabel(key: string): string;
|
|
263
1032
|
/**
|
|
@@ -287,24 +1056,660 @@ export declare function buildOwnedScalarUpdate(typeConfig: any): Record<string,
|
|
|
287
1056
|
* GET; without it (fresh type or a failed fetch) nothing is reported cleared.
|
|
288
1057
|
*/
|
|
289
1058
|
export declare function ownedScalarsBeingCleared(updateData: Record<string, any>, serverConfig: any): string[];
|
|
1059
|
+
/** The look-ahead a type-config PATCH carries about this push's operations. */
|
|
1060
|
+
export interface PendingOpClaims {
|
|
1061
|
+
pendingOpDeletes: string[];
|
|
1062
|
+
finalOpNames: string[];
|
|
1063
|
+
pendingOpUpdates: Array<{
|
|
1064
|
+
name: string;
|
|
1065
|
+
access: string | null;
|
|
1066
|
+
params: any;
|
|
1067
|
+
}>;
|
|
1068
|
+
pendingOpUpserts: Array<Record<string, any>>;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* What this push's operation set will BE, stated to the server (issues #813,
|
|
1072
|
+
* #1336, #2732).
|
|
1073
|
+
*
|
|
1074
|
+
* The type-config PATCH runs its gates BEFORE the same push's operation calls,
|
|
1075
|
+
* so without a look-ahead every gate judges the pre-push operations: a push
|
|
1076
|
+
* that removes a model is blocked by the very references it is deleting. The
|
|
1077
|
+
* file IS the target state (config-as-code), so the claims are derived from it:
|
|
1078
|
+
*
|
|
1079
|
+
* - `finalOpNames` — the names the type ends with. Both other claims are
|
|
1080
|
+
* verified against it server-side, which is what keeps them from being a
|
|
1081
|
+
* gate bypass for a direct-API caller.
|
|
1082
|
+
* - `pendingOpDeletes` — its complement among the ops last sync recorded, so
|
|
1083
|
+
* an op this push deletes is excluded from the schema / OPS_EXIST gates.
|
|
1084
|
+
* - `pendingOpUpserts` — the post-push BODY of every declared op, so the
|
|
1085
|
+
* schema-edit gate lints a rewritten op as rewritten and a model removal
|
|
1086
|
+
* plus its operation rewrites lands in one push (#2732).
|
|
1087
|
+
* - `pendingOpUpdates` — the rule-only form (`access`/`params`) the manifest
|
|
1088
|
+
* re-lint reads (#1336).
|
|
1089
|
+
*
|
|
1090
|
+
* All four are built here and sent together, on the dry-run and on the real
|
|
1091
|
+
* PATCH alike. They are never conditioned on the push having a deletion: a push
|
|
1092
|
+
* that only REWRITES operations is exactly the case #2732 exists for.
|
|
1093
|
+
*/
|
|
1094
|
+
export declare function buildPendingOpClaims(operations: any[], existingOpNames: string[]): PendingOpClaims;
|
|
290
1095
|
export declare function parseDatabaseTypeToml(tomlData: any): {
|
|
291
1096
|
typeConfig: any;
|
|
292
1097
|
operations: any[];
|
|
293
1098
|
subscriptions: any[];
|
|
294
1099
|
};
|
|
1100
|
+
/**
|
|
1101
|
+
* The parsed shape of a rule-set TOML: the definition's fields plus the
|
|
1102
|
+
* structural `[rules]` tree. Identical to the create body, which is what
|
|
1103
|
+
* `config diff` hashes both sides through.
|
|
1104
|
+
*/
|
|
295
1105
|
export declare function parseRuleSetToml(tomlData: any): any;
|
|
296
1106
|
export declare function parseGroupTypeConfigToml(tomlData: any): any;
|
|
297
1107
|
export declare function parseCollectionTypeConfigToml(tomlData: any): any;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
1108
|
+
/**
|
|
1109
|
+
* `config diff`'s hash for one configuration object, over the field set its
|
|
1110
|
+
* DEFINITION declares (#2644 criterion 8).
|
|
1111
|
+
*
|
|
1112
|
+
* Both sides — the local file and the server entity — run through this one
|
|
1113
|
+
* function, so a field is visible to `config diff` exactly when the definition
|
|
1114
|
+
* exposes it. Each type used to carry a hand-written
|
|
1115
|
+
* `hashLocal*ForDiff` / `hashRemote*ForDiff` pair naming its own fields: a
|
|
1116
|
+
* third copy of the field surface, with the same failure mode as the other two
|
|
1117
|
+
* (a field pushed and pulled but missing from the hash is invisible to diff, so
|
|
1118
|
+
* an edit reads as "nothing differs" until push applies it).
|
|
1119
|
+
*
|
|
1120
|
+
* `extras` carries the values that are NOT definition fields and must still be
|
|
1121
|
+
* compared: the authored sub-trees the definition classifies `structural`
|
|
1122
|
+
* (`[rules]`, `[[operations]]`, the `[metadata]` manifest) and the
|
|
1123
|
+
* `_unresolvedRuleSetName` marker below. `undefined` entries are dropped so an
|
|
1124
|
+
* absent extra and an omitted one hash the same.
|
|
1125
|
+
*/
|
|
1126
|
+
export declare function projectConfigForComparison(table: ConfigTable, entity: any, extras?: Record<string, any>): Record<string, any>;
|
|
1127
|
+
export declare function hashConfigForDiff(table: ConfigTable, entity: any, extras?: Record<string, any>): string;
|
|
1128
|
+
/**
|
|
1129
|
+
* The fields two projected records disagree on — what a conflict report prints
|
|
1130
|
+
* (#2731 B5) and what the immutable-field pre-check reads (B8).
|
|
1131
|
+
*
|
|
1132
|
+
* Compared over the UNION of keys, so a key present on one side only is a
|
|
1133
|
+
* difference rather than a silently ignored one. Values compare by canonical
|
|
1134
|
+
* JSON, which is key-order insensitive for objects and order-SENSITIVE for
|
|
1135
|
+
* arrays — a reordered array is a real change, per the comparator caveats.
|
|
1136
|
+
* Structural sub-trees (`rules`, `schema`, `metadataManifest`, operations,
|
|
1137
|
+
* subscriptions) arrive as single extras keys and therefore compare as units.
|
|
1138
|
+
*/
|
|
1139
|
+
export declare function diffProjectedRecords(local: Record<string, any>, remote: Record<string, any>): Array<{
|
|
1140
|
+
field: string;
|
|
1141
|
+
local: any;
|
|
1142
|
+
server: any;
|
|
1143
|
+
}>;
|
|
1144
|
+
/**
|
|
1145
|
+
* The differing fields the definition says an UPDATE does not accept (#2731 B8).
|
|
1146
|
+
*
|
|
1147
|
+
* A local edit to such a field (rule-set `resourceType`, `writableOn:
|
|
1148
|
+
* CREATE_ONLY`) would PATCH "successfully" while changing nothing server-side,
|
|
1149
|
+
* and then re-report forever — the exact shape of non-convergence this issue
|
|
1150
|
+
* exists to end. Extras keys are not definition fields and are never named.
|
|
1151
|
+
*/
|
|
1152
|
+
export declare function findImmutableFieldDiffs(table: ConfigTable, fieldDiffs: ReadonlyArray<{
|
|
1153
|
+
field: string;
|
|
1154
|
+
}>): string[];
|
|
1155
|
+
/**
|
|
1156
|
+
* Which side of a converted resource changed (#2731 B4).
|
|
1157
|
+
*
|
|
1158
|
+
* `baselineHash` is the manifest's `semanticHash`: the comparator's hash of the
|
|
1159
|
+
* state both sides last agreed on. With it the answer is exact. Without it —
|
|
1160
|
+
* a manifest written before this issue landed — the direction is INFERRED from
|
|
1161
|
+
* the legacy signals rather than defaulting to "apply": a stored `contentHash`
|
|
1162
|
+
* still matching the file's bytes proves the local side did not move, and a
|
|
1163
|
+
* stored `modifiedAt` still matching the live one proves the server did not.
|
|
1164
|
+
* When neither signal establishes a side (or the row was just adopted), the
|
|
1165
|
+
* answer is `unknown-direction`, which callers treat as a conflict needing
|
|
1166
|
+
* `config pull` or `--force`. An upgraded installation's first push must never
|
|
1167
|
+
* silently overwrite server drift.
|
|
1168
|
+
*/
|
|
1169
|
+
export type PushChangeClass = "unchanged" | "local-edited" | "server-drifted" | "both-changed" | "unknown-direction";
|
|
1170
|
+
export declare function classifyPushChange(input: {
|
|
1171
|
+
localHash: string;
|
|
1172
|
+
remoteHash: string;
|
|
1173
|
+
baselineHash?: string;
|
|
1174
|
+
legacy?: {
|
|
1175
|
+
localBytesMatchStoredContentHash?: boolean;
|
|
1176
|
+
liveModifiedAtMatchesStored?: boolean;
|
|
1177
|
+
};
|
|
1178
|
+
}): PushChangeClass;
|
|
1179
|
+
/** The rule-set name/id maps `config diff` resolves references through. */
|
|
1180
|
+
export interface ConfigDiffMaps {
|
|
1181
|
+
ruleSetIdToName: Map<string, string>;
|
|
1182
|
+
ruleSetNameToId: Map<string, string>;
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* How one configuration type is hashed for `config diff`: its definition, the
|
|
1186
|
+
* definition-driven parse both sides go through, the pull serializer that turns
|
|
1187
|
+
* a server entity into the file pull would have written, and the structural
|
|
1188
|
+
* values hashed alongside the definition's fields.
|
|
1189
|
+
*/
|
|
1190
|
+
export interface ConfigDiffSpec {
|
|
1191
|
+
label: string;
|
|
1192
|
+
table: ConfigTable;
|
|
1193
|
+
/**
|
|
1194
|
+
* TOML doc -> the wire-shaped entity (the same parse `config push` uses).
|
|
1195
|
+
*
|
|
1196
|
+
* `extra` is the per-type context the projection needs and the document does
|
|
1197
|
+
* not carry (#2880): a test case's id→name lookups, through which the two
|
|
1198
|
+
* spellings of a reference meet. Mirrors `serialize`, which has taken one
|
|
1199
|
+
* since the database types joined.
|
|
1200
|
+
*/
|
|
1201
|
+
parse(doc: any, extra?: any): any;
|
|
1202
|
+
/** Server entity -> the TOML `config pull` would write. */
|
|
1203
|
+
serialize(record: any, maps: ConfigDiffMaps, extra?: any): string;
|
|
1204
|
+
/** Structural values to hash beside the definition's fields. */
|
|
1205
|
+
extras?(entity: any, doc: any, extra?: any): Record<string, any>;
|
|
1206
|
+
/**
|
|
1207
|
+
* Rewrite the parsed entity the way the SERVER rewrites an accepted one
|
|
1208
|
+
* (#2880 DSO-003).
|
|
1209
|
+
*
|
|
1210
|
+
* Some handlers canonicalize on the way in — an integration's base URL gains
|
|
1211
|
+
* a trailing slash, its methods are upper-cased — so the state the server
|
|
1212
|
+
* returns is not the text the file holds. Comparing them raw makes an
|
|
1213
|
+
* authored-but-noncanonical value `local-edited` on every run and push
|
|
1214
|
+
* re-apply the same update forever; restamping the baseline cannot fix it,
|
|
1215
|
+
* because the local side never moves. Applied to both sides (the remote
|
|
1216
|
+
* reaches it through the same projection), so it must be idempotent.
|
|
1217
|
+
*/
|
|
1218
|
+
canonicalize?(entity: any): void;
|
|
1219
|
+
/**
|
|
1220
|
+
* The projected keys THIS FILE does not manage (#2880 criterion 3).
|
|
1221
|
+
*
|
|
1222
|
+
* A handful of fields are present-only by design: push sends them when the
|
|
1223
|
+
* file spells them and leaves the server's value alone when it does not,
|
|
1224
|
+
* because "absent" here means "not authored here" rather than "cleared" — a
|
|
1225
|
+
* webhook with no `[verification]` section at all, whose signing material a
|
|
1226
|
+
* push must not revoke as a side effect. Comparing such a field against a
|
|
1227
|
+
* server that holds one reports a difference push will never act on: the
|
|
1228
|
+
* update omits the key, the server keeps its value, and the row comes back
|
|
1229
|
+
* Modified on every run. So the local side inherits the remote value for
|
|
1230
|
+
* exactly the keys the file leaves unmanaged, and the two commands agree
|
|
1231
|
+
* that there is nothing to do.
|
|
1232
|
+
*/
|
|
1233
|
+
unmanaged?(doc: any): string[];
|
|
1234
|
+
/** Whether the entity carries a rule-set reference needing resolution. */
|
|
1235
|
+
ruleSetRef?: boolean;
|
|
1236
|
+
/** How the type names itself in the rule-set resolution message. */
|
|
1237
|
+
describe(entity: any): string;
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Every type the shared comparator serves — the types `config diff` compares
|
|
1241
|
+
* field-for-field, and that `config push` gates on the same projection
|
|
1242
|
+
* (#2731 B1/B2; webhooks joined the comparator in #2757 and their push joined
|
|
1243
|
+
* the same gate in #2880, so no registered type answers "changed?" from the
|
|
1244
|
+
* file's bytes any more).
|
|
1245
|
+
*
|
|
1246
|
+
* Published so the round-trip acceptance bar (#2731 B9) can require a fixture
|
|
1247
|
+
* per type instead of listing them a second time by hand: converting a type is
|
|
1248
|
+
* then one edit here, and the bar says so if its round trip is untested.
|
|
1249
|
+
*/
|
|
1250
|
+
export declare function configDiffSpecLabels(): string[];
|
|
1251
|
+
/** The diff spec for a configuration type. Throws rather than skipping a check. */
|
|
1252
|
+
export declare function configDiffSpec(label: string): ConfigDiffSpec;
|
|
1253
|
+
/**
|
|
1254
|
+
* Project a LOCAL config file onto the record both `config diff` and `config push`
|
|
1255
|
+
* compare (#2644 field set, #2731 shared gate). The hash is a thin wrapper, so
|
|
1256
|
+
* a push that needs to SAY what differs and a diff that only needs to know THAT
|
|
1257
|
+
* something differs read the same projection.
|
|
1258
|
+
*/
|
|
1259
|
+
export declare function projectLocalConfig(spec: ConfigDiffSpec, parsedToml: any, maps: ConfigDiffMaps, extra?: any): Record<string, any>;
|
|
1260
|
+
/** The same projection for a SERVER entity — see `hashRemoteConfigForDiff`. */
|
|
1261
|
+
export declare function projectRemoteConfig(spec: ConfigDiffSpec, record: any, maps: ConfigDiffMaps, extra?: any): Record<string, any>;
|
|
1262
|
+
/**
|
|
1263
|
+
* BOTH sides of one comparison, with the file's unmanaged keys reconciled
|
|
1264
|
+
* (#2880) — the one place `spec.unmanaged` is honored, so `config diff` and
|
|
1265
|
+
* `config push` cannot read the same file differently.
|
|
1266
|
+
*/
|
|
1267
|
+
export declare function projectConfigPair(spec: ConfigDiffSpec, localParsed: any, remoteRecord: any, maps: ConfigDiffMaps, extra?: any, localExtra?: any): {
|
|
1268
|
+
local: Record<string, any>;
|
|
1269
|
+
remote: Record<string, any>;
|
|
1270
|
+
};
|
|
1271
|
+
/**
|
|
1272
|
+
* One `config diff` row's content verdict for a registered type (#2880).
|
|
1273
|
+
*
|
|
1274
|
+
* The three outcomes the per-type blocks were each spelling out by hand:
|
|
1275
|
+
* equal (`exists`), different (`modified`, framed as a preview of
|
|
1276
|
+
* `config pull`), and "could not tell" — a missing record or a comparison that
|
|
1277
|
+
* threw, which degrades THIS row and never the whole diff. Written once so a
|
|
1278
|
+
* type joining the comparator cannot accidentally report a fourth thing.
|
|
1279
|
+
*
|
|
1280
|
+
* The two `extra` arguments are the same split `decidePushForConfig` makes:
|
|
1281
|
+
* `extra` carries what only the SERVER side has (a database type's operation
|
|
1282
|
+
* rows), while `localExtra` is context BOTH sides read a value through — a test
|
|
1283
|
+
* case's id→name lookups. Giving the local side nothing was a silent
|
|
1284
|
+
* mistranslation: a sidecar pinned by a resolvable `configId` projected the id
|
|
1285
|
+
* while the server's projection resolved it to the name, so an untouched file
|
|
1286
|
+
* reported Modified in diff and drifted in push.
|
|
1287
|
+
*/
|
|
1288
|
+
export declare function compareLocalToRemote(spec: ConfigDiffSpec, localParsed: any, remoteRecord: any, maps?: ConfigDiffMaps, extra?: any, localExtra?: any): {
|
|
1289
|
+
status: string;
|
|
1290
|
+
hint?: string;
|
|
1291
|
+
};
|
|
1292
|
+
/** Hash a LOCAL config file's definition-projected field set (#2644). */
|
|
1293
|
+
export declare function hashLocalConfigForDiff(spec: ConfigDiffSpec, parsedToml: any, maps: ConfigDiffMaps, extra?: any): string;
|
|
1294
|
+
/**
|
|
1295
|
+
* Hash a SERVER entity the same way, by serializing it into the file `sync
|
|
1296
|
+
* pull` would write and hashing that — so the two sides are normalized
|
|
1297
|
+
* identically by construction, and a legacy encoding on disk (a JSON-string
|
|
1298
|
+
* operation, an id-based rule-set reference) is not a false `Modified`.
|
|
1299
|
+
*
|
|
1300
|
+
* `extra` carries the sibling rows a type's file also holds: a database type's
|
|
1301
|
+
* operations and subscriptions, which the list response does not include.
|
|
1302
|
+
*/
|
|
1303
|
+
export declare function hashRemoteConfigForDiff(spec: ConfigDiffSpec, record: any, maps: ConfigDiffMaps, extra?: any): string;
|
|
1304
|
+
/** One field's disagreement between the local file and the live server entity. */
|
|
1305
|
+
export interface PushFieldDiff {
|
|
1306
|
+
field: string;
|
|
1307
|
+
local: any;
|
|
1308
|
+
server: any;
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* What `config push` should do with one converted-type file, and why.
|
|
1312
|
+
*
|
|
1313
|
+
* `create`/`skip`/`update` are the applying outcomes; `drift`, `conflict`,
|
|
1314
|
+
* `immutable` and `live-unavailable` are the four ways push declines to apply
|
|
1315
|
+
* and says so.
|
|
1316
|
+
*/
|
|
1317
|
+
export type PushGateOutcome = {
|
|
1318
|
+
action: "live-unavailable";
|
|
1319
|
+
} | {
|
|
1320
|
+
action: "create";
|
|
1321
|
+
} | {
|
|
1322
|
+
action: "skip";
|
|
1323
|
+
localHash: string;
|
|
1324
|
+
remoteHash: string;
|
|
1325
|
+
} | {
|
|
1326
|
+
action: "update";
|
|
1327
|
+
direction: "local-edited" | "forced" | "adopt-untracked";
|
|
1328
|
+
localHash: string;
|
|
1329
|
+
remoteHash?: string;
|
|
1330
|
+
expectedModifiedAt?: string;
|
|
1331
|
+
fields: PushFieldDiff[];
|
|
1332
|
+
} | {
|
|
1333
|
+
action: "drift";
|
|
1334
|
+
localHash: string;
|
|
1335
|
+
remoteHash: string;
|
|
1336
|
+
fields: PushFieldDiff[];
|
|
1337
|
+
} | {
|
|
1338
|
+
action: "conflict";
|
|
1339
|
+
direction: "both-changed" | "unknown-direction";
|
|
1340
|
+
localHash: string;
|
|
1341
|
+
remoteHash: string;
|
|
1342
|
+
fields: PushFieldDiff[];
|
|
1343
|
+
serverModifiedAt?: string;
|
|
1344
|
+
} | {
|
|
1345
|
+
action: "immutable";
|
|
1346
|
+
localHash: string;
|
|
1347
|
+
remoteHash: string;
|
|
1348
|
+
fields: PushFieldDiff[];
|
|
1349
|
+
immutableFields: string[];
|
|
1350
|
+
};
|
|
1351
|
+
/** The manifest entry a converted type's gate reads (all fields optional). */
|
|
1352
|
+
export interface PushBaselineEntry {
|
|
1353
|
+
modifiedAt?: string;
|
|
1354
|
+
contentHash?: string;
|
|
1355
|
+
semanticHash?: string;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* `config push`'s change decision for one configuration file (#2731 B1/B3/B4/B8).
|
|
1359
|
+
*
|
|
1360
|
+
* This replaces `shouldPushFile` for the converted types. The differences that
|
|
1361
|
+
* matter, in order:
|
|
1362
|
+
*
|
|
1363
|
+
* - the comparison is `config diff`'s — the definition-projected field set on
|
|
1364
|
+
* both sides — so "skip as unchanged" and "Synced" cannot disagree, and a
|
|
1365
|
+
* comment-only or formatting-only edit is not a change;
|
|
1366
|
+
* - the right-hand side is the LIVE entity, not the manifest, so out-of-band
|
|
1367
|
+
* server drift is visible instead of being overwritten;
|
|
1368
|
+
* - the manifest becomes a baseline that answers WHICH side moved, so a
|
|
1369
|
+
* difference is attributed rather than assumed to be a local edit.
|
|
1370
|
+
*
|
|
1371
|
+
* Declining is a first-class outcome here. `drift`, `conflict` and `immutable`
|
|
1372
|
+
* each carry the field diff their report prints, and nothing is applied.
|
|
1373
|
+
*
|
|
1374
|
+
* `adoptsUntrackedByKey` is the one exception a type can ask for, and it is
|
|
1375
|
+
* documented on the field: an object the manifest has never recorded is the
|
|
1376
|
+
* adopt its create path would have performed via a 409 (#1006/#2909), not a
|
|
1377
|
+
* difference to refuse.
|
|
1378
|
+
*/
|
|
1379
|
+
/**
|
|
1380
|
+
* What the DEGRADED gate does with one file — the path a converted type takes
|
|
1381
|
+
* when its live read failed (#2880 criterion 7).
|
|
1382
|
+
*
|
|
1383
|
+
* Falling back to the manifest's byte hash is right while there is one to fall
|
|
1384
|
+
* back to. Without one, `shouldPushFile(file, undefined)` answers "push it",
|
|
1385
|
+
* so a fresh checkout or a lost manifest turned "we could not read the server"
|
|
1386
|
+
* into "overwrite the server", unconditionally and silently — the one shape of
|
|
1387
|
+
* blind write this issue's direction attribution exists to prevent. So a
|
|
1388
|
+
* baseline-less update DECLINES: the resource is not written and the operator
|
|
1389
|
+
* is told which of `config pull` / `--force` clears it.
|
|
1390
|
+
*
|
|
1391
|
+
* A file the manifest does not name is declined for the same reason, and this
|
|
1392
|
+
* is the part that is easy to get wrong: an unrecorded file looks like a
|
|
1393
|
+
* create, but only LIVE STATE can say the server has nothing under that key —
|
|
1394
|
+
* and live state is exactly what this path could not read. The create that
|
|
1395
|
+
* follows is adopted by key on the 409 and re-issued as an update, so
|
|
1396
|
+
* "there is no recorded entity to overwrite" would have been a blind overwrite
|
|
1397
|
+
* of a resource this run never compared. Every section's degraded gate is
|
|
1398
|
+
* reached only when the live read failed, so there is no case here where
|
|
1399
|
+
* absence was proved.
|
|
1400
|
+
*/
|
|
1401
|
+
export type DegradedPushDecision = {
|
|
1402
|
+
action: "skip";
|
|
1403
|
+
} | {
|
|
1404
|
+
action: "send";
|
|
1405
|
+
} | {
|
|
1406
|
+
action: "decline";
|
|
1407
|
+
reason: "no-baseline";
|
|
1408
|
+
};
|
|
1409
|
+
export declare function decideDegradedPush(input: {
|
|
1410
|
+
force?: boolean;
|
|
1411
|
+
storedContentHash?: string;
|
|
1412
|
+
currentFileHash?: string;
|
|
1413
|
+
}): DegradedPushDecision;
|
|
1414
|
+
export declare function decidePushForConfig(input: {
|
|
1415
|
+
spec: ConfigDiffSpec;
|
|
1416
|
+
maps: ConfigDiffMaps;
|
|
1417
|
+
localParsed: any;
|
|
1418
|
+
/** The live server entity, or null/undefined when the server has none. */
|
|
1419
|
+
live?: {
|
|
1420
|
+
record: any;
|
|
1421
|
+
extra?: any;
|
|
1422
|
+
modifiedAt?: string;
|
|
1423
|
+
} | null;
|
|
1424
|
+
/** The manifest entry, absent when this file has never been synced. */
|
|
1425
|
+
entry?: PushBaselineEntry | null;
|
|
1426
|
+
/** Byte hash of the local file — the legacy direction signal. */
|
|
1427
|
+
localFileHash?: string;
|
|
1428
|
+
/**
|
|
1429
|
+
* Per-type context for the LOCAL projection (#2880) — a test case's id→name
|
|
1430
|
+
* lookups. Separate from `live.extra`, which carries remote-only rows (a
|
|
1431
|
+
* database type's operations) the local parse must never see.
|
|
1432
|
+
*/
|
|
1433
|
+
localExtra?: any;
|
|
1434
|
+
force?: boolean;
|
|
1435
|
+
/**
|
|
1436
|
+
* Whether this type's push path ADOPTS an existing object by key (#2909).
|
|
1437
|
+
*
|
|
1438
|
+
* Opt-in, and it changes exactly one outcome: a live entity the manifest has
|
|
1439
|
+
* no row for at all. Prompts (#1006), and the other types whose create path
|
|
1440
|
+
* recovers a 409 through `adoptByKeyOnCreate409`, treat that as the adopt it
|
|
1441
|
+
* has always been — an object pushed from another slot, created out of band,
|
|
1442
|
+
* or orphaned by a push that aborted before recording it, which the local
|
|
1443
|
+
* file is the declared intent for. Without this the gate reads that same
|
|
1444
|
+
* shape as a difference it cannot attribute, refuses, and the create the
|
|
1445
|
+
* adopt guard recovers from is never even sent (the #2909 regression).
|
|
1446
|
+
*
|
|
1447
|
+
* #2934 — the criterion is the CREATE PATH, not which issue converted the
|
|
1448
|
+
* type: cron triggers, webhooks, integrations, blob buckets and the
|
|
1449
|
+
* group/collection type configs all recover their create's key conflict the
|
|
1450
|
+
* same way, so they pass it too. Passing it at one call site left the other
|
|
1451
|
+
* five reporting a conflict for the adopt their own create documents.
|
|
1452
|
+
*
|
|
1453
|
+
* Rule sets keep it OFF, and that is a decision rather than an omission:
|
|
1454
|
+
* #2731 made a never-synced rule set differing from a same-named live one the
|
|
1455
|
+
* conflict the operator resolves with `config pull` or `--force`, its own
|
|
1456
|
+
* tests pin that, and `resourceType` — the field a wrong adopt would silently
|
|
1457
|
+
* strand — is one an update cannot repair.
|
|
1458
|
+
*/
|
|
1459
|
+
adoptsUntrackedByKey?: boolean;
|
|
1460
|
+
}): PushGateOutcome;
|
|
1461
|
+
/**
|
|
1462
|
+
* The identity a file may leave to its name, written back into the parsed
|
|
1463
|
+
* document before the shared gate reads it (#2731 B1).
|
|
1464
|
+
*
|
|
1465
|
+
* Four types let the file name stand in for the identity field inside the file
|
|
1466
|
+
* (`orders.toml` for a database type, `group.profile.toml` for a metadata
|
|
1467
|
+
* category, and so on). The gate re-parses the DOCUMENT, so the derived value
|
|
1468
|
+
* has to be in it: an omitted `databaseType` otherwise projects as absent
|
|
1469
|
+
* against a server record that carries it, and — since these identities are
|
|
1470
|
+
* declared create-only — the file reports an immutable-field difference on
|
|
1471
|
+
* every push while `config diff`, which injects the same value before it hashes
|
|
1472
|
+
* (~sync.ts:11744), reads it Synced.
|
|
1473
|
+
*
|
|
1474
|
+
* A value the file states always wins; this only fills the gap the file name
|
|
1475
|
+
* was already filling for the rest of push.
|
|
1476
|
+
*/
|
|
1477
|
+
export declare function withDerivedIdentity(doc: any, tomlPath: string, identity: Record<string, string | undefined>): any;
|
|
1478
|
+
/**
|
|
1479
|
+
* Which server-side entity this file's update targets (#2731 B3).
|
|
1480
|
+
*
|
|
1481
|
+
* The manifest is a cache, so it can be wrong in both directions: it can name
|
|
1482
|
+
* an entity the server no longer has (pointing an update at a 404 where the
|
|
1483
|
+
* operator asked for a re-create, #1659), and it can be silent about one that
|
|
1484
|
+
* is there (sending a create that comes back 409 to be adopted). When live
|
|
1485
|
+
* state was read, live state answers; the manifest is the fallback for the
|
|
1486
|
+
* degraded path only, where it is also what the byte-hash gate has always used.
|
|
1487
|
+
*/
|
|
1488
|
+
export declare function resolveExistingId(input: {
|
|
1489
|
+
liveOk: boolean;
|
|
1490
|
+
liveId?: string;
|
|
1491
|
+
manifestId?: string;
|
|
1492
|
+
}): string | undefined;
|
|
1493
|
+
/**
|
|
1494
|
+
* Which CHILD rows a database type's push reconciles against (#2731 B3).
|
|
1495
|
+
*
|
|
1496
|
+
* `resolveExistingId` answers this for a whole entity; a database type's
|
|
1497
|
+
* operations and subscriptions are rows with the same question and the same
|
|
1498
|
+
* two possible answers. The manifest can name an operation the server no
|
|
1499
|
+
* longer has (the update PUTs a row that is gone) and be silent about one it
|
|
1500
|
+
* does have (the create POSTs a duplicate key) — both abort the push with a
|
|
1501
|
+
* server error, which is exactly the recovery `--force` is supposed to
|
|
1502
|
+
* perform.
|
|
1503
|
+
*
|
|
1504
|
+
* When the reconcile read the rows, they ARE the baseline: existence, the
|
|
1505
|
+
* timestamp each update guards with, and — as the complement of the file's own
|
|
1506
|
+
* list — which rows this push deletes. `undefined` means the read did not
|
|
1507
|
+
* happen (the degraded path, or a type live state says is absent), and the
|
|
1508
|
+
* manifest stays what it has always been.
|
|
1509
|
+
*/
|
|
1510
|
+
export declare function liveChildBaseline(live: Map<string, any> | undefined, manifestChildren?: Record<string, {
|
|
1511
|
+
modifiedAt: string;
|
|
1512
|
+
}>): Record<string, {
|
|
1513
|
+
modifiedAt: string;
|
|
1514
|
+
}> | undefined;
|
|
1515
|
+
/**
|
|
1516
|
+
* The app's live rule sets, indexed the three ways push reads them (#2731 B3).
|
|
1517
|
+
*
|
|
1518
|
+
* One fetch answers three questions, and leaving any of them to the manifest
|
|
1519
|
+
* reintroduces the split this issue is about:
|
|
1520
|
+
*
|
|
1521
|
+
* - `byFileKey` — the rule set this file's own gate compares against;
|
|
1522
|
+
* - `idToName` — how a REFERENCING type's server record is serialized back
|
|
1523
|
+
* into the file pull would write, so a `ruleSetId` is compared as the name
|
|
1524
|
+
* the file spells;
|
|
1525
|
+
* - `nameToId` — how a referencing file's `ruleSetName` resolves to something
|
|
1526
|
+
* push can send. Seeding it from live is what lets `--only
|
|
1527
|
+
* group-type-config/team` resolve a rule set that exists on the server but
|
|
1528
|
+
* was never recorded locally, instead of failing as an unresolved
|
|
1529
|
+
* reference.
|
|
1530
|
+
*/
|
|
1531
|
+
export declare function indexLiveRuleSets(ruleSets: any[]): {
|
|
1532
|
+
byFileKey: Map<string, any>;
|
|
1533
|
+
idToName: Map<string, string>;
|
|
1534
|
+
nameToId: Map<string, string>;
|
|
1535
|
+
};
|
|
1536
|
+
/**
|
|
1537
|
+
* The baseline a successful apply stamps, from the SERVER's own record
|
|
1538
|
+
* (#2731 B4).
|
|
1539
|
+
*
|
|
1540
|
+
* The baseline is compared against both sides on the next run, so it has to be
|
|
1541
|
+
* in the server's spelling: handlers normalize what they store (rule-set
|
|
1542
|
+
* create/update trims `name`), and a baseline taken from the local file would
|
|
1543
|
+
* make the very next legitimate local edit read as "both sides changed" — a
|
|
1544
|
+
* conflict the operator can only clear with `--force`.
|
|
1545
|
+
*
|
|
1546
|
+
* `undefined` — no baseline — is the honest answer when the response carries
|
|
1547
|
+
* nothing projectable and no refetch is available. The next run then falls back
|
|
1548
|
+
* to the legacy `contentHash`/`modifiedAt` signals, which is what an upgraded
|
|
1549
|
+
* installation runs on anyway; an invented baseline would instead assert
|
|
1550
|
+
* agreement that was never observed.
|
|
1551
|
+
*/
|
|
1552
|
+
export declare function remoteSemanticBaseline(input: {
|
|
1553
|
+
spec: ConfigDiffSpec;
|
|
1554
|
+
maps: ConfigDiffMaps;
|
|
1555
|
+
/** What the write returned. Usable only if it is the stored entity. */
|
|
1556
|
+
returned?: any;
|
|
1557
|
+
extra?: any;
|
|
1558
|
+
/**
|
|
1559
|
+
* How this type recognizes its own stored record. The default — it carries a
|
|
1560
|
+
* `modifiedAt` — is what separates the stored entity from a bare
|
|
1561
|
+
* `{ success: true }` acknowledgement, whose empty projection would otherwise
|
|
1562
|
+
* be stamped as the state both sides agreed on. Types whose comparator reads
|
|
1563
|
+
* an envelope (an email template's `{ emailType, hasOverride, override }`)
|
|
1564
|
+
* say where their timestamp actually lives.
|
|
1565
|
+
*/
|
|
1566
|
+
isStoredRecord?: (returned: any) => boolean;
|
|
1567
|
+
}): string | undefined;
|
|
1568
|
+
/** What `config push` decides for one transform (#2731 B7). */
|
|
1569
|
+
export type ScriptPushOutcome = {
|
|
1570
|
+
action: "skip";
|
|
1571
|
+
localHash: string;
|
|
1572
|
+
remoteHash: string;
|
|
1573
|
+
} | {
|
|
1574
|
+
action: "update";
|
|
1575
|
+
direction: "local-edited" | "forced" | "no-active-body";
|
|
1576
|
+
localHash: string;
|
|
1577
|
+
} | {
|
|
1578
|
+
action: "drift";
|
|
1579
|
+
localHash: string;
|
|
1580
|
+
remoteHash: string;
|
|
1581
|
+
} | {
|
|
1582
|
+
action: "conflict";
|
|
1583
|
+
direction: "both-changed" | "unknown-direction";
|
|
1584
|
+
localHash: string;
|
|
1585
|
+
remoteHash: string;
|
|
1586
|
+
};
|
|
1587
|
+
/**
|
|
1588
|
+
* `config push`'s change decision for one transform (#2731 B7).
|
|
1589
|
+
*
|
|
1590
|
+
* A transform's comparator IS its bytes: the body push sends is the body diff
|
|
1591
|
+
* compares, so there is no projection to reconcile — only the same three
|
|
1592
|
+
* questions every converted type asks. Local versus the LIVE active body, with
|
|
1593
|
+
* the manifest demoted to the baseline that says which side moved.
|
|
1594
|
+
*
|
|
1595
|
+
* A script the manifest has never seen reaches here too (adopted by name from
|
|
1596
|
+
* the live index). It has no baseline and no legacy signal, so an unequal body
|
|
1597
|
+
* classifies as `unknown-direction` — a conflict to report rather than a server
|
|
1598
|
+
* body to overwrite on a guess.
|
|
1599
|
+
*/
|
|
1600
|
+
export declare function decidePushForScript(input: {
|
|
1601
|
+
localBody: string;
|
|
1602
|
+
/** Byte hash of the local file — the legacy direction signal. */
|
|
1603
|
+
localFileHash?: string;
|
|
1604
|
+
/**
|
|
1605
|
+
* The live active body, and the script row's timestamp beside it — the other
|
|
1606
|
+
* legacy direction signal (#2731 B4). Activating a config updates the script
|
|
1607
|
+
* row, so a timestamp still equal to the manifest's proves the server body
|
|
1608
|
+
* has not moved: an upgraded installation's first push applies a genuine
|
|
1609
|
+
* local edit instead of reporting a direction it could have known.
|
|
1610
|
+
*
|
|
1611
|
+
* `null` is an ANSWER, not a failed read: the script row exists and nothing
|
|
1612
|
+
* is active on it. See below.
|
|
1613
|
+
*/
|
|
1614
|
+
live: {
|
|
1615
|
+
body: string;
|
|
1616
|
+
modifiedAt?: string;
|
|
1617
|
+
} | null;
|
|
1618
|
+
entry?: {
|
|
1619
|
+
contentHash?: string;
|
|
1620
|
+
semanticHash?: string;
|
|
1621
|
+
modifiedAt?: string;
|
|
1622
|
+
} | null;
|
|
1623
|
+
force?: boolean;
|
|
1624
|
+
}): ScriptPushOutcome;
|
|
1625
|
+
/**
|
|
1626
|
+
* One side of a field diff, rendered for a conflict report (#2731 B5).
|
|
1627
|
+
*
|
|
1628
|
+
* A rule set's `rules` tree or a template's HTML body can be kilobytes; the
|
|
1629
|
+
* useful statement about them is that they differ and how big they are, not
|
|
1630
|
+
* their contents scrolling past.
|
|
1631
|
+
*/
|
|
1632
|
+
export declare function summarizeFieldValue(value: any): string;
|
|
1633
|
+
/**
|
|
1634
|
+
* The push summary line (#2731 criterion 10).
|
|
1635
|
+
*
|
|
1636
|
+
* The two established spellings are preserved exactly; drift — resources push
|
|
1637
|
+
* deliberately did NOT apply — is appended only when there is some, so an
|
|
1638
|
+
* operator never has to infer it from a change count that stayed put.
|
|
1639
|
+
*/
|
|
1640
|
+
export declare function formatPushSummary(input: {
|
|
1641
|
+
pushed: number;
|
|
1642
|
+
skipped: number;
|
|
1643
|
+
drifted: number;
|
|
1644
|
+
}): string;
|
|
1645
|
+
/**
|
|
1646
|
+
* The summary line a FAILED push ends on (#2731 B5).
|
|
1647
|
+
*
|
|
1648
|
+
* "Re-run `config push` to converge" is true of one failure only: a database
|
|
1649
|
+
* type the validate-first gate blocked (#813), where the next push carries the
|
|
1650
|
+
* corrected state. A conflict is the opposite — the run declined to apply
|
|
1651
|
+
* precisely because both sides moved, so repeating it reports the same
|
|
1652
|
+
* conflict forever (the alpha.62 field report ends in that loop). Apply
|
|
1653
|
+
* failures, which used to be absent from this line entirely, need the file
|
|
1654
|
+
* fixed rather than either.
|
|
1655
|
+
*/
|
|
1656
|
+
export declare function formatPushFailureSummary(input: {
|
|
1657
|
+
pushed: number;
|
|
1658
|
+
blockedDatabaseTypes: number;
|
|
1659
|
+
conflicts: number;
|
|
1660
|
+
applyFailures: number;
|
|
1661
|
+
}): string;
|
|
1662
|
+
/**
|
|
1663
|
+
* How many of a failed push's planned changes actually applied.
|
|
1664
|
+
*
|
|
1665
|
+
* `changes` records what push SET OUT to do, so the honest success count is
|
|
1666
|
+
* that list minus everything the run then refused or the server rejected:
|
|
1667
|
+
*
|
|
1668
|
+
* - a validate-first blocked database type (issue #813) keeps its labels for
|
|
1669
|
+
* visibility, but none of them landed — including its operations;
|
|
1670
|
+
* - a conflict on a change that was already counted (a late 409, recorded
|
|
1671
|
+
* before the write) cancels that change.
|
|
1672
|
+
*
|
|
1673
|
+
* A conflict the GATE declined (`planned: false`) is deliberately NOT
|
|
1674
|
+
* subtracted: it never added a `changes` entry, so charging the count for it
|
|
1675
|
+
* reports a push that did apply something as having applied nothing.
|
|
1676
|
+
*/
|
|
1677
|
+
export declare function countAppliedChanges(input: {
|
|
1678
|
+
changes: Array<{
|
|
1679
|
+
type: string;
|
|
1680
|
+
key: string;
|
|
1681
|
+
}>;
|
|
1682
|
+
conflicts: Array<{
|
|
1683
|
+
planned?: boolean;
|
|
1684
|
+
}>;
|
|
1685
|
+
blockedDatabaseTypes: string[];
|
|
1686
|
+
schemaBlockedCount: number;
|
|
1687
|
+
}): number;
|
|
1688
|
+
/**
|
|
1689
|
+
* The semantic baseline to stamp for a file both sides now agree on (#2731 B4).
|
|
1690
|
+
*
|
|
1691
|
+
* `contentHash` records what the local BYTES were; this records what the
|
|
1692
|
+
* comparator SAW, which is what makes "who changed?" answerable: after a pull
|
|
1693
|
+
* or a successful push, local and server agree, so one hash is the baseline for
|
|
1694
|
+
* both. Best-effort by design — an unreadable or unparseable file yields
|
|
1695
|
+
* `undefined`, and a missing baseline degrades to the legacy inference rather
|
|
1696
|
+
* than taking the command down.
|
|
1697
|
+
*/
|
|
1698
|
+
export declare function configSemanticHash(label: string, filePath: string, maps: ConfigDiffMaps): string | undefined;
|
|
1699
|
+
/**
|
|
1700
|
+
* The sync state `config push` writes into (#2731 B6, closing #2374).
|
|
1701
|
+
*
|
|
1702
|
+
* `loadSyncState` answers `null` for an absent `.primitive-sync.json`, and
|
|
1703
|
+
* every per-entity state write in the push loops is guarded by `if (syncState)`
|
|
1704
|
+
* — so a first push created every resource and recorded none of them. The next
|
|
1705
|
+
* push then re-entered the create path for everything and depended on 409
|
|
1706
|
+
* adoption to recover, which for database types hard-failed on operations.
|
|
1707
|
+
*
|
|
1708
|
+
* Building the state up front (the same shape the cross-app-push branch
|
|
1709
|
+
* already builds) makes those guards hold from the first run, so the
|
|
1710
|
+
* end-of-push and catch-path saves persist what the push actually did.
|
|
1711
|
+
*/
|
|
1712
|
+
export declare function ensurePushSyncState(existing: SyncState | null, appId: string, serverUrl: string): SyncState;
|
|
308
1713
|
export declare function parseTomlFile(filePath: string): any;
|
|
309
1714
|
/**
|
|
310
1715
|
* Paginate through a list endpoint, collecting all items.
|
|
@@ -317,7 +1722,7 @@ export declare function fetchAll<T>(listFn: (params: {
|
|
|
317
1722
|
nextCursor?: string | null;
|
|
318
1723
|
}>, pageSize?: number, maxPages?: number): Promise<T[]>;
|
|
319
1724
|
/**
|
|
320
|
-
* Issue #976 / #1006: shared 409 → adopt-by-key recovery for `
|
|
1725
|
+
* Issue #976 / #1006: shared 409 → adopt-by-key recovery for `config push`
|
|
321
1726
|
* create paths. When a CREATE hits a per-app unique-key constraint, the resource
|
|
322
1727
|
* is already on the server but missing from local sync state: orphaned by a
|
|
323
1728
|
* prior push that aborted before recording it, a mid-apply crash, or an
|
|
@@ -370,14 +1775,58 @@ export declare function adoptByKeyOnCreate409<T>(opts: {
|
|
|
370
1775
|
*/
|
|
371
1776
|
export declare function resolveRuleSetReference(entityConfig: any, ruleSetNameToId: Map<string, string>, entityLabel: string, options?: {
|
|
372
1777
|
throwOnMissing?: boolean;
|
|
1778
|
+
hint?: string;
|
|
373
1779
|
}): void;
|
|
1780
|
+
/**
|
|
1781
|
+
* Rule-set name→ID for the rule sets a scoped push is NOT applying
|
|
1782
|
+
* (issue #2645, review follow-up).
|
|
1783
|
+
*
|
|
1784
|
+
* Database, group and collection type configs name their rule set by NAME and
|
|
1785
|
+
* resolve it through the map the rule-set apply loop fills in as it goes. Under
|
|
1786
|
+
* `--only` that loop iterates the selected files alone, so `config push --only
|
|
1787
|
+
* database-type-config/orders` threw "Rule set … not found" for a type whose rule set
|
|
1788
|
+
* the operator had no reason to select — the single-object apply path refusing
|
|
1789
|
+
* an ordinary object, which is the opposite of what `--only` is for.
|
|
1790
|
+
*
|
|
1791
|
+
* A rule set the sync state already carries an id for is resolvable without
|
|
1792
|
+
* being applied, which is exactly what a reference needs. One that has never
|
|
1793
|
+
* been pushed still cannot resolve: there is no id to point at, and inventing
|
|
1794
|
+
* one would silently create the reference against nothing. That case keeps
|
|
1795
|
+
* throwing, with the hint to widen the selection.
|
|
1796
|
+
*
|
|
1797
|
+
* Pure, so the seeding is unit-testable without a server or a filesystem.
|
|
1798
|
+
*/
|
|
1799
|
+
export declare function unselectedRuleSetIds(params: {
|
|
1800
|
+
/** Every rule-set file in the slot. */
|
|
1801
|
+
files: string[];
|
|
1802
|
+
/** The subset this push is applying — seeded entries never shadow these. */
|
|
1803
|
+
selected: string[];
|
|
1804
|
+
/** The id sync state holds for a rule-set file's key, if it has one. */
|
|
1805
|
+
idForFileKey: (fileKey: string) => string | undefined;
|
|
1806
|
+
/** The `ruleSet.name` a file declares — the name references use. */
|
|
1807
|
+
nameForFile: (file: string) => string | undefined;
|
|
1808
|
+
}): Map<string, string>;
|
|
374
1809
|
export declare function slugifyTestCaseName(name: string): string;
|
|
375
|
-
|
|
1810
|
+
/**
|
|
1811
|
+
* The name to use for `slug`, given the names already taken.
|
|
1812
|
+
*
|
|
1813
|
+
* `occupy` decides what "already taken" means, and `usedSlugs` holds names in
|
|
1814
|
+
* that form (#2896): a file name is occupied CASE-INSENSITIVELY on the
|
|
1815
|
+
* filesystems most of these trees live on, so `Foo` and `foo` are one file even
|
|
1816
|
+
* though they are two strings. The returned name keeps its own casing — only
|
|
1817
|
+
* the occupancy check is normalized.
|
|
1818
|
+
*/
|
|
1819
|
+
export declare function resolveSlugCollisions(slug: string, usedSlugs: Set<string>, occupy?: (name: string) => string): string;
|
|
1820
|
+
export declare function getTestsDir(configDir: string, blockType: string, blockKey: string): string;
|
|
376
1821
|
export interface TestCaseLookupMaps {
|
|
377
1822
|
configIdToName: Map<string, string>;
|
|
378
1823
|
promptIdToKey: Map<string, string>;
|
|
379
1824
|
}
|
|
380
|
-
export declare function serializeTestCase(testCase: any, lookupMaps?: TestCaseLookupMaps
|
|
1825
|
+
export declare function serializeTestCase(testCase: any, lookupMaps?: TestCaseLookupMaps, options?: {
|
|
1826
|
+
/** The sidecar path the warning names, e.g. `prompts/greet.tests/a.toml`. */
|
|
1827
|
+
file?: string;
|
|
1828
|
+
logger?: (message: string) => void;
|
|
1829
|
+
}): string;
|
|
381
1830
|
export declare function parseTestCaseToml(tomlData: any): any;
|
|
382
1831
|
/**
|
|
383
1832
|
* Pull server-side `Script` rows into `transforms/*.rhai` and record
|
|
@@ -395,7 +1844,7 @@ export declare function parseTestCaseToml(tomlData: any): any;
|
|
|
395
1844
|
* (the old behavior) always produced 0-byte files.
|
|
396
1845
|
*
|
|
397
1846
|
* Idempotency: every call writes the active-config body the server
|
|
398
|
-
* returned, so re-running `
|
|
1847
|
+
* returned, so re-running `config pull` on an unchanged server overwrites
|
|
399
1848
|
* with the same bytes and produces the same `contentHash`. The result
|
|
400
1849
|
* map always reflects the current server state for the writes performed.
|
|
401
1850
|
*
|
|
@@ -410,11 +1859,17 @@ export declare function parseTestCaseToml(tomlData: any): any;
|
|
|
410
1859
|
* `getScript` failure is caught and that one script is skipped without
|
|
411
1860
|
* aborting the rest of the pull.
|
|
412
1861
|
*/
|
|
413
|
-
export declare function pullScripts(client: ApiClient, appId: string, configDir: string, logger?: (msg: string) => void
|
|
1862
|
+
export declare function pullScripts(client: ApiClient, appId: string, configDir: string, logger?: (msg: string) => void,
|
|
1863
|
+
/**
|
|
1864
|
+
* `config pull --only` (issue #2645): which transform names this pull may
|
|
1865
|
+
* write. Defaults to every one the server lists.
|
|
1866
|
+
*/
|
|
1867
|
+
selects?: (name: string) => boolean): Promise<{
|
|
414
1868
|
scriptEntities: Record<string, {
|
|
415
1869
|
id: string;
|
|
416
1870
|
modifiedAt: string;
|
|
417
1871
|
contentHash?: string;
|
|
1872
|
+
semanticHash?: string;
|
|
418
1873
|
}>;
|
|
419
1874
|
count: number;
|
|
420
1875
|
/**
|
|
@@ -436,5 +1891,433 @@ export declare function pullScripts(client: ApiClient, appId: string, configDir:
|
|
|
436
1891
|
/** Names the server listed, empty when the listing failed. */
|
|
437
1892
|
serverKeys: string[];
|
|
438
1893
|
}>;
|
|
439
|
-
|
|
1894
|
+
/** The outcome of one block's test-case pull (#2769). */
|
|
1895
|
+
export type TestCasePullOutcome =
|
|
1896
|
+
/** The listing succeeded; `count` cases were written and stale files removed. */
|
|
1897
|
+
{
|
|
1898
|
+
ok: true;
|
|
1899
|
+
count: number;
|
|
1900
|
+
}
|
|
1901
|
+
/** The listing failed: nothing was written, and the caller preserves state. */
|
|
1902
|
+
| {
|
|
1903
|
+
ok: false;
|
|
1904
|
+
};
|
|
1905
|
+
/**
|
|
1906
|
+
* Every test case a block has, draining the cursor (#2769).
|
|
1907
|
+
*
|
|
1908
|
+
* The endpoint returns 50 per page. Reconciling the sidecar against page one
|
|
1909
|
+
* alone would delete every file past it and drop the ids that keep push from
|
|
1910
|
+
* duplicating them, so the whole set is collected BEFORE anything is written.
|
|
1911
|
+
*/
|
|
1912
|
+
export declare function listAllTestCases(client: ApiClient, appId: string, blockType: TestBlockType, blockId: string): Promise<{
|
|
1913
|
+
ok: true;
|
|
1914
|
+
items: any[];
|
|
1915
|
+
} | {
|
|
1916
|
+
ok: false;
|
|
1917
|
+
}>;
|
|
1918
|
+
/**
|
|
1919
|
+
* Copy a block's prior `entities.testCases` records into the pull's fresh map
|
|
1920
|
+
* (#2769), returning how many were carried.
|
|
1921
|
+
*
|
|
1922
|
+
* `config pull` rebuilds the test-case slot from scratch, so a block whose
|
|
1923
|
+
* listing failed would silently lose its ids — and the next push would create a
|
|
1924
|
+
* second copy of every case it could no longer recognize. A failed fetch means
|
|
1925
|
+
* "unknown", so the prior picture stands.
|
|
1926
|
+
*/
|
|
1927
|
+
export declare function carryForwardTestCaseEntities(params: {
|
|
1928
|
+
prior: Record<string, any> | undefined;
|
|
1929
|
+
target: Record<string, any>;
|
|
1930
|
+
blockType: string;
|
|
1931
|
+
blockKey: string;
|
|
1932
|
+
}): number;
|
|
1933
|
+
/**
|
|
1934
|
+
* Write one block's test cases into its `<key>.tests/` sidecar and record them
|
|
1935
|
+
* in sync state. Exported so the unit tests can drive it against a stubbed
|
|
1936
|
+
* client on a temp directory.
|
|
1937
|
+
*/
|
|
1938
|
+
export declare function pullTestCasesForBlock(params: {
|
|
1939
|
+
client: ApiClient;
|
|
1940
|
+
appId: string;
|
|
1941
|
+
blockType: TestBlockType;
|
|
1942
|
+
blockId: string;
|
|
1943
|
+
blockKey: string;
|
|
1944
|
+
configDir: string;
|
|
1945
|
+
testCaseEntities: Record<string, any>;
|
|
1946
|
+
/** Last sync's records, for the fallbacks a partial failure falls back to. */
|
|
1947
|
+
priorTestCaseEntities?: Record<string, any>;
|
|
1948
|
+
lookupMaps?: TestCaseLookupMaps;
|
|
1949
|
+
logger?: (message: string) => void;
|
|
1950
|
+
}): Promise<TestCasePullOutcome>;
|
|
1951
|
+
/**
|
|
1952
|
+
* One pull leg: every block of a type the pull selected (#2769).
|
|
1953
|
+
*
|
|
1954
|
+
* The selection is the caller's — a leg is handed the blocks `--only` left in,
|
|
1955
|
+
* so a scoped pull never reads, writes or removes a sidecar it was not asked
|
|
1956
|
+
* about. A block whose listing failed keeps its prior state entries and is
|
|
1957
|
+
* reported as skipped rather than silently reconciled to empty.
|
|
1958
|
+
*/
|
|
1959
|
+
export declare function pullTestCasesForBlocks(params: {
|
|
1960
|
+
client: ApiClient;
|
|
1961
|
+
appId: string;
|
|
1962
|
+
configDir: string;
|
|
1963
|
+
blockType: TestBlockType;
|
|
1964
|
+
blocks: Array<{
|
|
1965
|
+
id: string;
|
|
1966
|
+
key: string;
|
|
1967
|
+
}>;
|
|
1968
|
+
testCaseEntities: Record<string, any>;
|
|
1969
|
+
priorTestCaseEntities?: Record<string, any>;
|
|
1970
|
+
lookupMaps?: TestCaseLookupMaps;
|
|
1971
|
+
logger?: (message: string) => void;
|
|
1972
|
+
}): Promise<{
|
|
1973
|
+
count: number;
|
|
1974
|
+
skippedBlocks: string[];
|
|
1975
|
+
}>;
|
|
1976
|
+
interface PushResolutionMaps {
|
|
1977
|
+
promptKeyToId: Map<string, string>;
|
|
1978
|
+
promptConfigNameToId: Map<string, string>;
|
|
1979
|
+
workflowConfigNameToId: Map<string, string>;
|
|
1980
|
+
scriptConfigNameToId?: Map<string, string>;
|
|
1981
|
+
integrationConfigNameToId?: Map<string, string>;
|
|
1982
|
+
/**
|
|
1983
|
+
* Fill one block's config name→id entries from the server on demand (#2769),
|
|
1984
|
+
* resolving `false` when the block has no id yet.
|
|
1985
|
+
*
|
|
1986
|
+
* A test case may pin a config on a block this push did not select, or run
|
|
1987
|
+
* under `--dry-run`, where nothing was written to learn the ids from. The
|
|
1988
|
+
* lookup is READ-ONLY and independent of both, so a name that a real push
|
|
1989
|
+
* resolves never reads as broken.
|
|
1990
|
+
*/
|
|
1991
|
+
loadBlockConfigs?: (blockType: TestBlockType, blockKey: string) => Promise<boolean>;
|
|
1992
|
+
/** Whether a block is authored locally but not on the server yet. */
|
|
1993
|
+
isPlannedBlock?: (blockType: TestBlockType, blockKey: string) => boolean;
|
|
1994
|
+
}
|
|
1995
|
+
/** A failure row the push reports and exits nonzero on (`applyFailures`). */
|
|
1996
|
+
type ApplyFailure = {
|
|
1997
|
+
type: string;
|
|
1998
|
+
key: string;
|
|
1999
|
+
message: string;
|
|
2000
|
+
};
|
|
2001
|
+
/** The attachment bookkeeping a test case's sync-state record carries. */
|
|
2002
|
+
type RecordedAttachments = {
|
|
2003
|
+
attachments?: Record<string, string>;
|
|
2004
|
+
attachmentFilenames?: string[];
|
|
2005
|
+
};
|
|
2006
|
+
/**
|
|
2007
|
+
* Which attachments a push must upload, and which the SERVER holds that the
|
|
2008
|
+
* sidecar no longer does (#2769).
|
|
2009
|
+
*
|
|
2010
|
+
* Comparison is by content hash: the pre-#2769 state recorded names only, so a
|
|
2011
|
+
* byte change under an unchanged name was invisible. A state entry still in the
|
|
2012
|
+
* old format has no hash to compare against, so every file counts as changed
|
|
2013
|
+
* and re-uploads once (an idempotent overwrite) — after which the state carries
|
|
2014
|
+
* hashes and the next push skips them.
|
|
2015
|
+
*
|
|
2016
|
+
* The removed set is a REPORT, not an action: deleting a remote attachment
|
|
2017
|
+
* because a local file is missing waits for `--prune`.
|
|
2018
|
+
*/
|
|
2019
|
+
export declare function planAttachmentPush(params: {
|
|
2020
|
+
local: Array<{
|
|
2021
|
+
filename: string;
|
|
2022
|
+
hash: string;
|
|
2023
|
+
}>;
|
|
2024
|
+
recorded: RecordedAttachments | undefined;
|
|
2025
|
+
}): {
|
|
2026
|
+
upload: string[];
|
|
2027
|
+
removedRemotely: string[];
|
|
2028
|
+
};
|
|
2029
|
+
/**
|
|
2030
|
+
* Create a test case carrying the identity its file name asserts (#2896).
|
|
2031
|
+
*
|
|
2032
|
+
* The one case protocol detection cannot answer is a create into an EMPTY
|
|
2033
|
+
* block: there is no listed record to read `key` off. Push is optimistic there,
|
|
2034
|
+
* and an older server's 400 is the answer — retried once without the key, and
|
|
2035
|
+
* named, because a case created without one will duplicate on the next clone.
|
|
2036
|
+
*/
|
|
2037
|
+
export declare function createTestCaseWithIdentity(params: {
|
|
2038
|
+
client: ApiClient;
|
|
2039
|
+
appId: string;
|
|
2040
|
+
blockType: TestBlockType;
|
|
2041
|
+
blockId: string;
|
|
2042
|
+
payload: any;
|
|
2043
|
+
/** Omitted when the server has no keys; then this is exactly the old call. */
|
|
2044
|
+
key?: string;
|
|
2045
|
+
logger?: (message: string) => void;
|
|
2046
|
+
}): Promise<any>;
|
|
2047
|
+
/**
|
|
2048
|
+
* Push one block's authored test-case sidecar. Exported so the unit tests can
|
|
2049
|
+
* drive it against a stubbed client on a temp directory.
|
|
2050
|
+
*
|
|
2051
|
+
* Plain push creates and updates only: a file the operator removed is reported
|
|
2052
|
+
* as a pending deletion and handled by `--prune` (`applyTestCasePrune`).
|
|
2053
|
+
*/
|
|
2054
|
+
export declare function pushTestCasesForBlock(params: {
|
|
2055
|
+
client: ApiClient;
|
|
2056
|
+
appId: string;
|
|
2057
|
+
blockType: TestBlockType;
|
|
2058
|
+
blockId: string;
|
|
2059
|
+
blockKey: string;
|
|
2060
|
+
configDir: string;
|
|
2061
|
+
syncState: SyncState | null;
|
|
2062
|
+
dryRun: boolean;
|
|
2063
|
+
changes: Array<{
|
|
2064
|
+
type: string;
|
|
2065
|
+
action: string;
|
|
2066
|
+
key: string;
|
|
2067
|
+
}>;
|
|
2068
|
+
/** Where a rejected create/update/upload goes — it fails the push (#2731 B2). */
|
|
2069
|
+
failures: ApplyFailure[];
|
|
2070
|
+
/**
|
|
2071
|
+
* What push DECLINED to apply (#2880): server drift, a conflict, a live read
|
|
2072
|
+
* that failed with no baseline. Reported exactly as every other converted
|
|
2073
|
+
* type reports it — the caller passes its `recordDeclined`.
|
|
2074
|
+
*/
|
|
2075
|
+
declined?: (type: string, key: string, outcome: Extract<PushGateOutcome, {
|
|
2076
|
+
action: "drift" | "conflict" | "immutable" | "live-unavailable";
|
|
2077
|
+
}>, storedModifiedAt?: string) => void;
|
|
2078
|
+
/** The id→name lookups the comparison reads a reference through (#2880). */
|
|
2079
|
+
lookupMaps?: TestCaseLookupMaps;
|
|
2080
|
+
/**
|
|
2081
|
+
* Re-derive `lookupMaps` from the caller's name→id maps (#2880). Called after
|
|
2082
|
+
* this case's references are resolved and before it is compared: resolution
|
|
2083
|
+
* is what LOADS a block's configs, so the comparison would otherwise read a
|
|
2084
|
+
* reference the same push just learned how to read.
|
|
2085
|
+
*/
|
|
2086
|
+
refreshLookupMaps?: () => void;
|
|
2087
|
+
resolutionMaps?: PushResolutionMaps;
|
|
2088
|
+
options?: {
|
|
2089
|
+
force?: boolean;
|
|
2090
|
+
};
|
|
2091
|
+
}): Promise<{
|
|
2092
|
+
skipped: number;
|
|
2093
|
+
}>;
|
|
2094
|
+
/**
|
|
2095
|
+
* The file name each live test case belongs in (#2896).
|
|
2096
|
+
*
|
|
2097
|
+
* A KEYED case is named by its key: that is what the key is — the basename the
|
|
2098
|
+
* committed tree carries. An unkeyed one (created by web-admin, an older CLI or
|
|
2099
|
+
* the raw API) keeps the slug-of-name naming, with the collision suffixes
|
|
2100
|
+
* resolved only AFTER the keys have claimed theirs, so a slug can never take a
|
|
2101
|
+
* name a key owns.
|
|
2102
|
+
*
|
|
2103
|
+
* One function for pull, for pairing's adoption pass and for the diff's
|
|
2104
|
+
* remote-only rows, because three commands that name a case's file differently
|
|
2105
|
+
* is the disagreement this issue reports.
|
|
2106
|
+
*
|
|
2107
|
+
* Two names that differ only by CASE are one file on a case-insensitive
|
|
2108
|
+
* filesystem, so occupancy is tracked the way the server's key constraint
|
|
2109
|
+
* normalizes: a keyed `Foo` and an unkeyed case named "foo" get `Foo.toml` and
|
|
2110
|
+
* `foo-2.toml`, rather than one pull silently overwriting the other's sidecar.
|
|
2111
|
+
*/
|
|
2112
|
+
export declare function testCaseFileBasenames(liveCases: any[], onUnusableKey?: (live: any, reason: string) => void, isSafeName?: (name: string) => boolean): Map<any, string>;
|
|
2113
|
+
/**
|
|
2114
|
+
* Whether a sidecar and a live case say the same thing (#2896).
|
|
2115
|
+
*
|
|
2116
|
+
* The corroboration the adoption passes below need: identity nothing states can
|
|
2117
|
+
* only be inferred from content, and only when the inference is unique. It is
|
|
2118
|
+
* the SAME projection `config diff` compares with, memoized per file and per
|
|
2119
|
+
* record, so pairing and the change verdict can never disagree. A file that
|
|
2120
|
+
* cannot be read matches nothing — an unparseable sidecar fails its own push
|
|
2121
|
+
* with a message that names it.
|
|
2122
|
+
*/
|
|
2123
|
+
export declare function testCaseContentMatcher(params: {
|
|
2124
|
+
testsDir: string;
|
|
2125
|
+
spec: ConfigDiffSpec;
|
|
2126
|
+
extra?: any;
|
|
2127
|
+
}): (localSlug: string, live: any) => boolean;
|
|
2128
|
+
/**
|
|
2129
|
+
* Which live test case each sidecar manages (#2880 behavior 16, #2896).
|
|
2130
|
+
*
|
|
2131
|
+
* The manifest id comes FIRST, then the identity the COMMITTED tree carries —
|
|
2132
|
+
* the file's basename, matched against the case's stored `key`. Everything
|
|
2133
|
+
* after that is adoption of a case whose identity nothing states: a rename the
|
|
2134
|
+
* manifest still remembers under the old name, a legacy case matched by
|
|
2135
|
+
* content, and finally the slug-of-name rule #2880 shipped.
|
|
2136
|
+
*
|
|
2137
|
+
* The order matters because every pass consumes its claims: identity that IS
|
|
2138
|
+
* recorded always wins over a guess, and a guess is only made when it is
|
|
2139
|
+
* unambiguous in BOTH directions. Where it is not, the file stays unpaired and
|
|
2140
|
+
* is barred from the create path — push refusing to guess is the whole point,
|
|
2141
|
+
* since the failure it replaces is a silently duplicated test case.
|
|
2142
|
+
*
|
|
2143
|
+
* Against a server with no keys the passes that depend on them are skipped
|
|
2144
|
+
* entirely, so pairing is bit-for-bit what #2880 shipped.
|
|
2145
|
+
*
|
|
2146
|
+
* One function for both commands, because a diff row and a push decision that
|
|
2147
|
+
* pair differently are two answers to the same question.
|
|
2148
|
+
*/
|
|
2149
|
+
export declare function pairTestCases(input: {
|
|
2150
|
+
blockType: string;
|
|
2151
|
+
blockKey: string;
|
|
2152
|
+
localSlugs: string[];
|
|
2153
|
+
liveCases: any[];
|
|
2154
|
+
managed: Record<string, {
|
|
2155
|
+
id?: string;
|
|
2156
|
+
slug?: string;
|
|
2157
|
+
blockKey?: string;
|
|
2158
|
+
}> | undefined;
|
|
2159
|
+
/** Whether the server carries identity keys at all (#2896). */
|
|
2160
|
+
serverSupportsKeys?: boolean;
|
|
2161
|
+
/** Whether a local sidecar's content equals a live case's projection. */
|
|
2162
|
+
contentMatches?: (localSlug: string, live: any) => boolean;
|
|
2163
|
+
}): {
|
|
2164
|
+
bySlug: Map<string, any>;
|
|
2165
|
+
localOnly: string[];
|
|
2166
|
+
remoteOnly: any[];
|
|
2167
|
+
/** Files adopted from a stale manifest entry: basename → that entry's key. */
|
|
2168
|
+
renamedFrom: Map<string, string>;
|
|
2169
|
+
/** Files barred from the create path this run, with the reason to report. */
|
|
2170
|
+
refused: Map<string, string>;
|
|
2171
|
+
};
|
|
2172
|
+
/**
|
|
2173
|
+
* `config diff` for one block's test-case sidecar (#2769). A failed listing is
|
|
2174
|
+
* an OUTCOME the caller reports as "not compared" — the old helper returned
|
|
2175
|
+
* silently, so a block whose tests could not be fetched simply disappeared from
|
|
2176
|
+
* a report that still read as exhaustive.
|
|
2177
|
+
*/
|
|
2178
|
+
export interface TestCaseDiffRow {
|
|
2179
|
+
blockType: string;
|
|
2180
|
+
blockKey: string;
|
|
2181
|
+
slug: string;
|
|
2182
|
+
status: string;
|
|
2183
|
+
/** What a validation error or a degraded comparison has to say. */
|
|
2184
|
+
hint?: string;
|
|
2185
|
+
/** Attachments push would upload from this sidecar (#2880 behavior 19). */
|
|
2186
|
+
attachmentUploads?: string[];
|
|
2187
|
+
/** Managed attachments `push --prune` would delete (never a plain push). */
|
|
2188
|
+
attachmentDeletions?: string[];
|
|
2189
|
+
}
|
|
2190
|
+
export declare function compareTestCasesForBlock(params: {
|
|
2191
|
+
client: ApiClient;
|
|
2192
|
+
appId: string;
|
|
2193
|
+
blockType: TestBlockType;
|
|
2194
|
+
blockId: string;
|
|
2195
|
+
blockKey: string;
|
|
2196
|
+
configDir: string;
|
|
2197
|
+
/** The manifest — identity, and the attachment hashes the plan reads. */
|
|
2198
|
+
syncState?: SyncState | null;
|
|
2199
|
+
lookupMaps?: TestCaseLookupMaps;
|
|
2200
|
+
}): Promise<{
|
|
2201
|
+
ok: true;
|
|
2202
|
+
rows: TestCaseDiffRow[];
|
|
2203
|
+
} | {
|
|
2204
|
+
ok: false;
|
|
2205
|
+
}>;
|
|
2206
|
+
/**
|
|
2207
|
+
* Shape errors in an authored test-case sidecar, for the push preflight
|
|
2208
|
+
* (#2769). The unknown-key check beside it says which keys may appear; this
|
|
2209
|
+
* says whether the ones that are there can be pushed at all — a missing `name`
|
|
2210
|
+
* is a 400 from the server, and malformed JSON text used to be silently dropped
|
|
2211
|
+
* on the way to it, pushing a test case that tested something else.
|
|
2212
|
+
*/
|
|
2213
|
+
export declare function testCaseSidecarErrors(filePath: string, tomlData: any): string[];
|
|
2214
|
+
/**
|
|
2215
|
+
* EVERY reason `config push` refuses one test-case sidecar (#2880 criterion 1).
|
|
2216
|
+
*
|
|
2217
|
+
* The four checks the push preflight ran inline: the document's shape, the keys
|
|
2218
|
+
* the definition recognizes, the declared type of each value, and the sidecar's
|
|
2219
|
+
* own authoring rules (#2769). Collected in one place because `config diff`
|
|
2220
|
+
* runs the identical list — it used to run only the last of the four, so a
|
|
2221
|
+
* sidecar with an unknown key or a mistyped ordinary field was reported Synced
|
|
2222
|
+
* by the command whose whole promise is that a Synced file pushes.
|
|
2223
|
+
*/
|
|
2224
|
+
export declare function testCaseSidecarPreflightErrors(filePath: string, tomlData: any): string[];
|
|
2225
|
+
/** A managed test case whose authored file is gone (#2769). */
|
|
2226
|
+
export interface TestCaseDeletionCandidate {
|
|
2227
|
+
stateKey: string;
|
|
2228
|
+
blockType: string;
|
|
2229
|
+
blockId: string;
|
|
2230
|
+
blockKey: string;
|
|
2231
|
+
slug: string;
|
|
2232
|
+
/** Absent when the case was never successfully created server-side. */
|
|
2233
|
+
id?: string;
|
|
2234
|
+
/**
|
|
2235
|
+
* The state entry — with a file still on disk — that holds this same id
|
|
2236
|
+
* (#2896). Set when the missing file was RENAMED rather than deleted: the
|
|
2237
|
+
* case is alive under another name, so the row is cleared and nothing is
|
|
2238
|
+
* deleted server-side.
|
|
2239
|
+
*/
|
|
2240
|
+
supersededBy?: string;
|
|
2241
|
+
}
|
|
2242
|
+
/** A managed attachment whose local file is gone (#2769). */
|
|
2243
|
+
export interface AttachmentDeletionCandidate extends TestCaseDeletionCandidate {
|
|
2244
|
+
filename: string;
|
|
2245
|
+
}
|
|
2246
|
+
/**
|
|
2247
|
+
* Managed test cases whose `<slug>.toml` the operator removed (#2769).
|
|
2248
|
+
*
|
|
2249
|
+
* Only under a block that is itself still managed: when the BLOCK's file is
|
|
2250
|
+
* gone too, the block's own prune owns the whole sidecar (`removePrunedSidecar`)
|
|
2251
|
+
* and listing its cases here would delete them twice over.
|
|
2252
|
+
*/
|
|
2253
|
+
export declare function collectTestCaseDeletions(params: {
|
|
2254
|
+
configDir: string;
|
|
2255
|
+
testCaseEntities: Record<string, any> | undefined;
|
|
2256
|
+
blockExists?: (blockType: string, blockKey: string) => boolean;
|
|
2257
|
+
}): TestCaseDeletionCandidate[];
|
|
2258
|
+
/**
|
|
2259
|
+
* Managed attachments whose local file is gone, for a test case that survives
|
|
2260
|
+
* (#2769). An absent attachment DIRECTORY is the authored spelling of "no
|
|
2261
|
+
* attachments" — Git cannot carry an empty directory — so its recorded files
|
|
2262
|
+
* are candidates too. Still `--prune`-gated: plain push deletes nothing.
|
|
2263
|
+
*/
|
|
2264
|
+
export declare function collectAttachmentDeletions(params: {
|
|
2265
|
+
configDir: string;
|
|
2266
|
+
testCaseEntities: Record<string, any> | undefined;
|
|
2267
|
+
blockExists?: (blockType: string, blockKey: string) => boolean;
|
|
2268
|
+
}): AttachmentDeletionCandidate[];
|
|
2269
|
+
/**
|
|
2270
|
+
* What a plain `config push` says about the sidecar deletions it is NOT
|
|
2271
|
+
* applying (#2769), one report for the whole push.
|
|
2272
|
+
*
|
|
2273
|
+
* Attachments belong here rather than in the per-block push: deleting only a
|
|
2274
|
+
* fixture leaves its test-case TOML untouched, so that push skips the case by
|
|
2275
|
+
* hash and never reaches a message of its own — exactly the case an operator
|
|
2276
|
+
* needs told, since the test keeps running against a file the tree no longer
|
|
2277
|
+
* has. Returns the lines to print; empty when nothing is pending.
|
|
2278
|
+
*/
|
|
2279
|
+
export declare function formatPendingTestDeletions(params: {
|
|
2280
|
+
testCases: TestCaseDeletionCandidate[];
|
|
2281
|
+
attachments: AttachmentDeletionCandidate[];
|
|
2282
|
+
}): string[];
|
|
2283
|
+
/**
|
|
2284
|
+
* Apply the test-case half of `config push --prune` (#2769).
|
|
2285
|
+
*
|
|
2286
|
+
* Runs after the batch confirmation, alongside the entity prunes: deletes the
|
|
2287
|
+
* cases and attachments whose authored files are gone, drops their state, and
|
|
2288
|
+
* routes failures into the push's failure list. Under `--dry-run` it counts the
|
|
2289
|
+
* same rows and calls nothing.
|
|
2290
|
+
*/
|
|
2291
|
+
export declare function applyTestCasePrune(params: {
|
|
2292
|
+
client: ApiClient;
|
|
2293
|
+
appId: string;
|
|
2294
|
+
testCaseEntities: Record<string, any>;
|
|
2295
|
+
candidates: TestCaseDeletionCandidate[];
|
|
2296
|
+
attachmentCandidates: AttachmentDeletionCandidate[];
|
|
2297
|
+
dryRun: boolean;
|
|
2298
|
+
changes: Array<{
|
|
2299
|
+
type: string;
|
|
2300
|
+
action: string;
|
|
2301
|
+
key: string;
|
|
2302
|
+
}>;
|
|
2303
|
+
failures: ApplyFailure[];
|
|
2304
|
+
logger?: (message: string) => void;
|
|
2305
|
+
}): Promise<{
|
|
2306
|
+
deleted: number;
|
|
2307
|
+
deletedAttachments: number;
|
|
2308
|
+
}>;
|
|
2309
|
+
/**
|
|
2310
|
+
* Attach the server-reconciling verbs to the `config` group (issue #2759).
|
|
2311
|
+
*
|
|
2312
|
+
* They used to be a top-level `sync` noun, which split one workflow — managing
|
|
2313
|
+
* the TOML configuration tree — across two nouns by whether a verb happened to
|
|
2314
|
+
* call the API. Users do not think in that distinction; every real workflow
|
|
2315
|
+
* crosses it. So `config` owns both halves, and this function takes the group
|
|
2316
|
+
* `registerConfigCommands` created rather than making one of its own.
|
|
2317
|
+
*
|
|
2318
|
+
* The module keeps its name and its ~50 exported helpers: `sync.ts`,
|
|
2319
|
+
* `resolveSyncDir`, `.primitive-sync.json` and `.primitive/sync/<env>/<appId>/`
|
|
2320
|
+
* are internal identifiers, deliberately left alone (#2759 scope).
|
|
2321
|
+
*/
|
|
2322
|
+
export declare function registerConfigSyncCommands(sync: Command): void;
|
|
440
2323
|
export {};
|