kalup 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/{commands-T4j_GVBD.mjs → commands-CHucC_PV.mjs} +2866 -597
- package/dist/commands.d.mts +1 -1
- package/dist/commands.mjs +1 -1
- package/dist/{context-C1tH5K0x.d.mts → context-B7bI9BjL.d.mts} +42 -0
- package/dist/{host-CDDeLV5T.mjs → host-D1HtgrVd.mjs} +1 -1
- package/dist/host.d.mts +1 -1
- package/dist/host.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/schemas/ir-1.schema.json +41 -1
- package/dist/schemas/plan-1.schema.json +18 -0
- package/docs/apply.md +5 -3
- package/docs/config.md +37 -8
- package/docs/dictionary.md +3 -3
- package/docs/errors/E_DUPLICATE_LABEL.md +24 -0
- package/docs/errors/E_MISSING_EXPORT.md +2 -2
- package/docs/errors/E_PIPELINE_FIELD.md +23 -0
- package/docs/errors/E_PIPELINE_ID.md +17 -0
- package/docs/errors/E_PIPELINE_STAGES.md +17 -0
- package/docs/errors/E_TOMBSTONE_ADDRESS.md +3 -3
- package/docs/errors/E_UNSUPPORTED_FILE.md +3 -3
- package/docs/errors/E_WRITE_IN_READ_MODE.md +1 -1
- package/docs/errors/E_WRITE_NOT_ALLOWED.md +1 -1
- package/docs/plan.md +20 -7
- package/docs/pull.md +10 -6
- package/docs/rm.md +6 -4
- package/docs/snapshot.md +2 -2
- package/docs/targets.md +2 -2
- package/package.json +2 -2
package/dist/commands.d.mts
CHANGED
package/dist/commands.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as TargetRebindCommand, a as CompareCommand, c as InitCommand, d as PlanCommand, f as PullCommand, g as StatusCommand, h as StateRebuildCommand, i as COMMANDS, l as IrCommand, m as SnapshotCommand, n as ApplyCommand, o as DocsCommand, p as RmCommand, r as BlueprintUpgradeCommand, s as FmtCommand, t as AddCommand, u as KalupCommand, v as ValidateCommand } from "./commands-
|
|
1
|
+
import { _ as TargetRebindCommand, a as CompareCommand, c as InitCommand, d as PlanCommand, f as PullCommand, g as StatusCommand, h as StateRebuildCommand, i as COMMANDS, l as IrCommand, m as SnapshotCommand, n as ApplyCommand, o as DocsCommand, p as RmCommand, r as BlueprintUpgradeCommand, s as FmtCommand, t as AddCommand, u as KalupCommand, v as ValidateCommand } from "./commands-CHucC_PV.mjs";
|
|
2
2
|
export { AddCommand, ApplyCommand, BlueprintUpgradeCommand, COMMANDS, CompareCommand, DocsCommand, FmtCommand, InitCommand, IrCommand, KalupCommand, PlanCommand, PullCommand, RmCommand, SnapshotCommand, StateRebuildCommand, StatusCommand, TargetRebindCommand, ValidateCommand };
|
|
@@ -297,6 +297,17 @@ declare const issues: {
|
|
|
297
297
|
output: string[];
|
|
298
298
|
};
|
|
299
299
|
};
|
|
300
|
+
E_DUPLICATE_LABEL: {
|
|
301
|
+
exit: string;
|
|
302
|
+
title: string;
|
|
303
|
+
summary: string;
|
|
304
|
+
when: string[];
|
|
305
|
+
fix: string[];
|
|
306
|
+
example: {
|
|
307
|
+
config: string[];
|
|
308
|
+
output: string[];
|
|
309
|
+
};
|
|
310
|
+
};
|
|
300
311
|
E_DUPLICATE_OPTION: {
|
|
301
312
|
exit: string;
|
|
302
313
|
title: string;
|
|
@@ -516,6 +527,37 @@ declare const issues: {
|
|
|
516
527
|
output: string[];
|
|
517
528
|
};
|
|
518
529
|
};
|
|
530
|
+
E_PIPELINE_FIELD: {
|
|
531
|
+
exit: string;
|
|
532
|
+
title: string;
|
|
533
|
+
summary: string;
|
|
534
|
+
when: string[];
|
|
535
|
+
fix: string[];
|
|
536
|
+
example: {
|
|
537
|
+
config: string[];
|
|
538
|
+
output: string[];
|
|
539
|
+
};
|
|
540
|
+
};
|
|
541
|
+
E_PIPELINE_ID: {
|
|
542
|
+
exit: string;
|
|
543
|
+
title: string;
|
|
544
|
+
summary: string;
|
|
545
|
+
when: string[];
|
|
546
|
+
fix: string[];
|
|
547
|
+
example: {
|
|
548
|
+
output: string[];
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
E_PIPELINE_STAGES: {
|
|
552
|
+
exit: string;
|
|
553
|
+
title: string;
|
|
554
|
+
summary: string;
|
|
555
|
+
when: string[];
|
|
556
|
+
fix: string[];
|
|
557
|
+
example: {
|
|
558
|
+
output: string[];
|
|
559
|
+
};
|
|
560
|
+
};
|
|
519
561
|
E_PLAN_DELETE: {
|
|
520
562
|
exit: string;
|
|
521
563
|
title: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as sanitize, C as usageError, D as disclaimer, E as bin, O as escapeJson, S as versionText, T as KalupError, b as formats, k as exitCodes, u as KalupCommand, w as IssueError, x as version, y as PATH_MAX } from "./commands-
|
|
1
|
+
import { A as sanitize, C as usageError, D as disclaimer, E as bin, O as escapeJson, S as versionText, T as KalupError, b as formats, k as exitCodes, u as KalupCommand, w as IssueError, x as version, y as PATH_MAX } from "./commands-CHucC_PV.mjs";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
package/dist/host.d.mts
CHANGED
package/dist/host.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as isInteractive, r as run, t as execute } from "./host-
|
|
1
|
+
import { n as isInteractive, r as run, t as execute } from "./host-D1HtgrVd.mjs";
|
|
2
2
|
export { execute, isInteractive, run };
|
package/dist/index.mjs
CHANGED
|
@@ -93,6 +93,14 @@
|
|
|
93
93
|
{
|
|
94
94
|
"if": { "required": ["type"], "properties": { "type": { "const": "object" } } },
|
|
95
95
|
"then": { "properties": { "definition": { "$ref": "#/$defs/objectDefinition" } } }
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"if": { "required": ["type"], "properties": { "type": { "const": "pipeline" } } },
|
|
99
|
+
"then": { "properties": { "definition": { "$ref": "#/$defs/pipelineDefinition" } } }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"if": { "required": ["type"], "properties": { "type": { "const": "stage" } } },
|
|
103
|
+
"then": { "properties": { "definition": { "$ref": "#/$defs/stageDefinition" } } }
|
|
96
104
|
}
|
|
97
105
|
]
|
|
98
106
|
},
|
|
@@ -185,6 +193,29 @@
|
|
|
185
193
|
},
|
|
186
194
|
"additionalProperties": false
|
|
187
195
|
},
|
|
196
|
+
"pipelineDefinition": {
|
|
197
|
+
"description": "A pipeline. Its stages are resources of their own; stages lists their IDs in display order.",
|
|
198
|
+
"type": "object",
|
|
199
|
+
"required": ["label", "displayOrder", "stages"],
|
|
200
|
+
"properties": {
|
|
201
|
+
"label": { "type": "string" },
|
|
202
|
+
"displayOrder": { "type": "integer", "minimum": 0 },
|
|
203
|
+
"stages": { "type": "array", "items": { "type": "string" } }
|
|
204
|
+
},
|
|
205
|
+
"additionalProperties": false
|
|
206
|
+
},
|
|
207
|
+
"stageDefinition": {
|
|
208
|
+
"description": "A pipeline stage. Its metadata field depends on the object: probability on deals, ticketState on tickets, state on custom objects.",
|
|
209
|
+
"type": "object",
|
|
210
|
+
"required": ["label"],
|
|
211
|
+
"properties": {
|
|
212
|
+
"label": { "type": "string" },
|
|
213
|
+
"probability": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
214
|
+
"ticketState": { "enum": ["OPEN", "CLOSED"] },
|
|
215
|
+
"state": { "enum": ["OPEN", "CLOSED"] }
|
|
216
|
+
},
|
|
217
|
+
"additionalProperties": false
|
|
218
|
+
},
|
|
188
219
|
"binding": {
|
|
189
220
|
"description": "App terms, filled in by the loader. key and codec for properties, export for objects.",
|
|
190
221
|
"type": "object",
|
|
@@ -322,7 +353,9 @@
|
|
|
322
353
|
"properties": {
|
|
323
354
|
"property": { "type": "array", "items": { "type": "string" } },
|
|
324
355
|
"group": { "type": "array", "items": { "type": "string" } },
|
|
325
|
-
"object": { "type": "array", "items": { "type": "string" } }
|
|
356
|
+
"object": { "type": "array", "items": { "type": "string" } },
|
|
357
|
+
"pipeline": { "type": "array", "items": { "type": "string" } },
|
|
358
|
+
"stage": { "type": "array", "items": { "type": "string" } }
|
|
326
359
|
},
|
|
327
360
|
"additionalProperties": false
|
|
328
361
|
}
|
|
@@ -380,6 +413,13 @@
|
|
|
380
413
|
"type": "object",
|
|
381
414
|
"patternProperties": { "^[a-z]+:\\S+$": { "type": "string" } },
|
|
382
415
|
"additionalProperties": false
|
|
416
|
+
},
|
|
417
|
+
"pipelines": {
|
|
418
|
+
"description": "Whether the object's pipelines list was read. Absent when its pipelines are not in scope.",
|
|
419
|
+
"type": "object",
|
|
420
|
+
"required": ["status"],
|
|
421
|
+
"properties": { "status": { "enum": ["read", "unreadable"] }, "missingScope": { "type": "string" } },
|
|
422
|
+
"additionalProperties": false
|
|
383
423
|
}
|
|
384
424
|
},
|
|
385
425
|
"additionalProperties": false
|
|
@@ -469,6 +469,24 @@
|
|
|
469
469
|
"description": "Logical form, $ref included, resolved at apply. A create holds the full definition.",
|
|
470
470
|
"type": "object"
|
|
471
471
|
},
|
|
472
|
+
"stages": {
|
|
473
|
+
"description": "A pipeline create only: the stages it carries, in display order, each with its full definition. HubSpot refuses a pipeline without a stage.",
|
|
474
|
+
"type": "array",
|
|
475
|
+
"items": {
|
|
476
|
+
"type": "object",
|
|
477
|
+
"required": ["address", "desired"],
|
|
478
|
+
"properties": {
|
|
479
|
+
"address": { "$ref": "#/$defs/address" },
|
|
480
|
+
"desired": { "type": "object" }
|
|
481
|
+
},
|
|
482
|
+
"additionalProperties": false
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"stageLabels": {
|
|
486
|
+
"description": "Display only, never approved: the label of each stage a pipeline step's stage order names, config's else the portal's, so the plan text shows labels and not IDs.",
|
|
487
|
+
"type": "object",
|
|
488
|
+
"additionalProperties": { "type": "string" }
|
|
489
|
+
},
|
|
472
490
|
"ignoreChanges": {
|
|
473
491
|
"description": "Create only: set on create, released after.",
|
|
474
492
|
"type": "array",
|
package/docs/apply.md
CHANGED
|
@@ -28,18 +28,20 @@ Before any write, in order:
|
|
|
28
28
|
6. Each delete has a `destroy` tombstone in `hubspot/removed.ts` or takeover's leave (takeover mode, in the pull scope, not excluded), is gone from config, and no address in config names its portal resource, read as data (`E_PLAN_DELETE`). The object files also tell takeover's option removals from config's own.
|
|
29
29
|
7. Approval, then the portal lock (`E_LOCKED`).
|
|
30
30
|
8. State: a plan already applied with outcome `done` exits 0 ("Already applied"); otherwise lineage and serial equal the plan's (`E_STATE_CHANGED`).
|
|
31
|
-
9. A fresh read of each object the plan changes, and of the schemas list for a custom object (`E_INCOMPLETE` on a 403, `E_BINDING_CHANGED` for another type ID). Every `expect` must hold (`E_PLAN_STALE`), a delete's covering each field its base holds. Kalup derives each step's risk, labels and blocked status again (`E_PLAN_RISK` when the plan states less); a takeover removal needs `allowDestroy`, and never takes a HubSpot-defined property.
|
|
31
|
+
9. A fresh read of each object the plan changes, its pipelines when a step touches one, and of the schemas list for a custom object (`E_INCOMPLETE` on a 403, `E_BINDING_CHANGED` for another type ID). Every `expect` must hold (`E_PLAN_STALE`), a delete's covering each field its base holds. Kalup derives each step's risk, labels and blocked status again (`E_PLAN_RISK` when the plan states less); a takeover removal needs `allowDestroy`, and never takes a HubSpot-defined property.
|
|
32
32
|
10. Three calls per write plus the reads use at most half of HubSpot's daily remainder (`E_BUDGET`).
|
|
33
33
|
|
|
34
34
|
## Running the steps
|
|
35
35
|
|
|
36
|
-
Apply records `lastApply.outcome: running`, then runs the steps one at a time: groups, properties, releases, then deletes
|
|
36
|
+
Apply records `lastApply.outcome: running`, then runs the steps one at a time: groups, properties, pipeline creates, stage creates and updates (those that close a stage first, so a ticket pipeline keeps a closed stage), pipeline updates, releases, then deletes: properties, groups, stages, pipelines. Each write reads the resource again and compares it with `expect`, builds the request from that read, sends it once, and reads it back for up to 60 seconds, saying so on stderr after a few seconds.
|
|
37
37
|
|
|
38
38
|
- A property update sends the approved fields with the live `type` and `fieldType`; options go as the full live list with the approved changes, new ones last.
|
|
39
|
+
- A pipeline create sends its stages with it. A stage update sends only the approved fields. A stage order change moves each stage that is out of place onto the slot of the stage it follows, one request each, reading the pipeline before each move: HubSpot renumbers the pipeline when a stage lands on a taken slot. If a move waits out a rate limit, the step stops stale; plan again to see what is left. Kalup never sends a pipeline PUT, which deletes every stage it does not name.
|
|
40
|
+
- A pipeline or stage is read back with a GET of its pipeline. A stage delete is done only when that read lacks the stage: HubSpot answers 204 to a delete of any stage, one that does not exist included.
|
|
39
41
|
- A 429, 423 or 477 is waited out three times, reading again before each resend. A daily 429 stops the run.
|
|
40
42
|
- A timeout, network failure or 5xx is `uncertain` and never resent: HubSpot documents no idempotency keys. Only reading back the approved values settles it (`E_UNCERTAIN_WRITE`).
|
|
41
43
|
- A value HubSpot stores differently is `W_UNVERIFIED`: state records both, and the next plan notes it instead of writing again.
|
|
42
|
-
- A delete runs only once every earlier step verified. HubSpot keeps an archived property restorable in its UI for 90 days.
|
|
44
|
+
- A delete runs only once every earlier step verified. HubSpot keeps an archived property restorable in its UI for 90 days; a deleted pipeline or stage is gone for good.
|
|
43
45
|
|
|
44
46
|
State is saved after each step that changes an entry, then the outcome: `done`, `partial` or `uncertain`. Each request is journaled in `.kalup/journal/portal-<id>/`, never with a key or body. SIGINT or SIGTERM stops before the next request and saves state. A second one exits at once, unless it comes within a second (npx passes one Ctrl-C on twice).
|
|
45
47
|
|
package/docs/config.md
CHANGED
|
@@ -8,14 +8,15 @@ This page is the reference. For the walk-through with examples, see [Config file
|
|
|
8
8
|
|
|
9
9
|
- `kalup.config.ts`: one `export default defineConfig({...})` and nothing after it. Fields: `name` (default: the name in the nearest `package.json` up to the repository root, else the directory name), `dir` (the folder of object files, relative to `kalup.config.ts` and inside the project, default `hubspot`; `E_SETTING_VALUE` otherwise), `state` (`'local'`, the default, or `'repo'`, state.md), `prefix`, `defaultTarget` (targets.md), `mode` (below), `objects` (the pull scope, pull.md) and `targets` (targets.md). A setting at a level that does not take it is `E_SETTING_LEVEL`, whose fix lists the levels that do; a value it does not take is `E_SETTING_VALUE`, with the nearest allowed one.
|
|
10
10
|
- `hubspot/objects/<object>.ts`: one or more `export const <Name> = defineObject('<object>', {...})` or `defineCustomObject('<name>', {...})`. The writer adds an `export type <Name>Data = ...` line after each. A file with no such export is `E_MISSING_EXPORT`.
|
|
11
|
-
- `hubspot/
|
|
11
|
+
- `hubspot/pipelines/<object>.ts`: one or more `export const <Name> = definePipeline('<object>', {...})`, below. A `definePipeline` export anywhere else is `E_UNSUPPORTED_FILE`.
|
|
12
|
+
- `hubspot/index.ts`: the barrel, written by `pull` and `fmt`. It imports each object file as `./objects/<object>.js` and each pipeline file as `./pipelines/<object>.js`, which resolves under TypeScript `NodeNext`, `Node16` and `Bundler` resolution, bundlers such as Vite and Next.js, and plain Node running `tsc` output. Under `NodeNext`, import it as `./hubspot/index.js`.
|
|
12
13
|
- `hubspot/removed.ts`: tombstones, below.
|
|
13
|
-
-
|
|
14
|
+
- A `defineConfig` or `defineRemoved` file elsewhere under `hubspot/` is `E_UNSUPPORTED_FILE`.
|
|
14
15
|
- `hubspot/blueprints.lock.json` and `hubspot/.blueprints/`: written by `kalup add` (blueprints.md).
|
|
15
16
|
|
|
16
17
|
The folder belongs to Kalup alone: every `.ts` file in it is read as config, `pull` rewrites its `index.ts`, and `init` takes it out of the formatter's checks. Point `dir` at a folder of its own (`lib/config/hubspot`, not `lib/config` next to the app's modules); `init` refuses a folder that holds other `.ts` files (`E_DIR_IN_USE`). A 0.1 project keeps its `kalup/` folder while `dir` is unset and `hubspot/` holds no `.ts` file, with `W_LEGACY_DIR` on every command until you set `dir: 'kalup'` or move the folder. When both hold `.ts` files, every command stops with `E_DIR_AMBIGUOUS` until `dir` says which.
|
|
17
18
|
|
|
18
|
-
Commit `kalup.config.ts` and the folder: the object files, `index.ts`, `removed.ts`, the blueprints lock, and `state/` only with `state: 'repo'` (state.md). Never commit `.kalup/` (local state, saved plans, journals, history, snapshots), a plan file, `.env` or any file holding a key.
|
|
19
|
+
Commit `kalup.config.ts` and the folder: the object and pipeline files, `index.ts`, `removed.ts`, the blueprints lock, and `state/` only with `state: 'repo'` (state.md). Never commit `.kalup/` (local state, saved plans, journals, history, snapshots), a plan file, `.env` or any file holding a key.
|
|
19
20
|
|
|
20
21
|
## The grammar
|
|
21
22
|
|
|
@@ -23,7 +24,7 @@ Anything else is `E_NOT_DATA`.
|
|
|
23
24
|
|
|
24
25
|
- `import` lines. Imports from `@kalup/core` and `kalup` are rewritten; others are kept, for `p.json` validators.
|
|
25
26
|
- Object literals of `key: value` entries, arrays, strings in single or double quotes on one line, numbers, `true` and `false`. No template strings, identifiers as values, spreads, computed keys, shorthand, or calls other than the builders.
|
|
26
|
-
- A `//` comment on its own line above an export, a group entry or a
|
|
27
|
+
- A `//` comment on its own line above an export, a group entry, a property entry or a stage entry, and a comment block above the imports (the file header). Every other comment is an error, including any in `kalup.config.ts` but the header.
|
|
27
28
|
- `p.<kind>('<internal name>')` or `p.<kind>('<internal name>', {...})`, then any of `.strict()` (`p.enum` and `p.multiEnum` only), `.required()`, `.readonly()` and `.managed(false)`, each once. Any other chain call is `E_BAD_CHAIN`. A kind not listed below is `E_UNKNOWN_BUILDER`.
|
|
28
29
|
- `p.json('<name>', <validator>, {...})`. The validator is opaque text and may not hold a `//` comment.
|
|
29
30
|
- A custom object needs `labels: { singular, plural }` and `primaryDisplayProperty`, and may set `requiredProperties`, `searchableProperties` and `secondaryDisplayProperties`.
|
|
@@ -78,13 +79,13 @@ The object key is the app's name for the property. Two exports of one object usi
|
|
|
78
79
|
|
|
79
80
|
## Per-target definitions
|
|
80
81
|
|
|
81
|
-
A target's override `definition` (targets.md) replaces each field it states there, whole, and owns it, empty values included: a property's `label`, `description`, `group`, `fieldType`, `formField`, `options` (no `as`), `hidden`, `displayOrder`, the display fields, `calculationFormula` and lifecycle but `preventDestroy`; a group's `label
|
|
82
|
+
A target's override `definition` (targets.md) replaces each field it states there, whole, and owns it, empty values included: a property's `label`, `description`, `group`, `fieldType`, `formField`, `options` (no `as`), `hidden`, `displayOrder`, the display fields, `calculationFormula` and lifecycle but `preventDestroy`; a group's `label`; a pipeline's `label` and `displayOrder`; a stage's `label` and metadata field. Else `E_OVERRIDE_DEFINITION`. `pull` writes these fields into the override.
|
|
82
83
|
|
|
83
84
|
## Mode: addon and takeover
|
|
84
85
|
|
|
85
86
|
`mode: 'addon' | 'takeover'` at the top level, under `objects.<object>`, under `targets.<target>`, or under `targets.<target>.objects.<object>`. The most specific wins, in that order from the last, and the default is `addon`: Kalup manages only what config names. A target `mode` that differs from an object's `mode` the target says nothing more about is `W_MODE_SHADOWED`.
|
|
86
87
|
|
|
87
|
-
Under `takeover`, `plan` archives every custom property and group in the object's pull scope that config lacks and `hubspot/removed.ts` does not name (a group only once every property in it goes, and after them), and removes enum options only the portal holds. Never a HubSpot-defined or calculated property, a kind Kalup does not write, anything an object file lists, a name `exclude` covers,
|
|
88
|
+
Under `takeover`, `plan` archives every custom property and group in the object's pull scope that config lacks and `hubspot/removed.ts` does not name (a group only once every property in it goes, and after them), and removes enum options only the portal holds. Never a HubSpot-defined or calculated property, a kind Kalup does not write, anything an object file lists, a name `exclude` covers, a property a custom object schema names, or a pipeline or stage. Every takeover removal is destructive: it needs `allowDestroy: true` on the target and a person at a terminal, and `--yes` and `--approve` never cover it. Without `allowDestroy` it is blocked, reason `policy`; after an incomplete read, reason `scope`.
|
|
88
89
|
|
|
89
90
|
## Removed resources
|
|
90
91
|
|
|
@@ -97,8 +98,36 @@ export default defineRemoved({
|
|
|
97
98
|
})
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
`destroy` deletes the resource, only on a target with `allowDestroy: true` (default false). `release` stops managing it, leaving it there. Keys are property or
|
|
101
|
+
`destroy` deletes the resource, only on a target with `allowDestroy: true` (default false). `release` stops managing it, leaving it there. Keys are property, group, pipeline or stage addresses (`E_TOMBSTONE_ADDRESS`) that config no longer defines (`E_TOMBSTONE_CONFLICT`). A pipeline's tombstone covers its stages. HubSpot keeps no archive of a pipeline or stage: a delete is permanent.
|
|
102
|
+
|
|
103
|
+
## Pipelines
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
// hubspot/pipelines/deals.ts
|
|
107
|
+
import { definePipeline } from '@kalup/core'
|
|
108
|
+
|
|
109
|
+
export const RenewalsPipeline = definePipeline('deals', {
|
|
110
|
+
id: 'renewals',
|
|
111
|
+
label: 'Renewals',
|
|
112
|
+
displayOrder: 2,
|
|
113
|
+
stages: {
|
|
114
|
+
open: { id: 'renewals_open', label: 'Open', probability: 0.2 },
|
|
115
|
+
won: { id: 'renewals_won', label: 'Won', probability: 1 },
|
|
116
|
+
lost: { id: 'renewals_lost', label: 'Lost', probability: 0 },
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- The first argument is the object key as in `objects`. Kalup writes the pipelines of `deals`, `tickets` and custom objects; the pipelines of contacts, companies, appointments, services, listings, courses, orders and leads are read and compared, never written. A pipeline on an object HubSpot gives none is `E_PIPELINE_FIELD`.
|
|
122
|
+
- `id` is the pipeline or stage ID HubSpot stores, and the address: `pipeline:deals/renewals`, `stage:deals/renewals/renewals_won`. It never changes; a new ID is a new pipeline or stage. A pipeline ID is at most 36 characters, a stage ID at most 100, neither holds whitespace or `/`, a pipeline ID is used once in the project and a stage ID once per object (`E_PIPELINE_ID`): HubSpot keeps pipeline IDs unique across objects and stage IDs across one object's pipelines.
|
|
123
|
+
- `label` and `displayOrder` are required on a pipeline, `label` on a stage. Labels update in place. Two stages of a pipeline, or two pipelines of an object, may not share a label, ignoring case (`E_DUPLICATE_LABEL`).
|
|
124
|
+
- Stage order is the order of the entries under `stages`. Stages take no `displayOrder`.
|
|
125
|
+
- Each stage takes the metadata field of its object, and no other (`E_PIPELINE_FIELD`): `probability` on a deal stage, required, from 0 to 1; `ticketState` on a ticket stage, `'OPEN'` or `'CLOSED'`, default `'OPEN'`; `state` on a custom object stage, the same. HubSpot derives whether a stage is closed from it.
|
|
126
|
+
- A pipeline needs a stage, and a ticket pipeline a stage with `ticketState: 'CLOSED'` (`E_PIPELINE_STAGES`).
|
|
127
|
+
- The export name and the stage keys are free. In the app, `RenewalsPipeline.stages.won.id` is typed `'renewals_won'`, and `StageId<typeof RenewalsPipeline>` is the union of its stage IDs.
|
|
128
|
+
|
|
129
|
+
A target's override takes a pipeline's or stage's ID on that portal as `name`, `skip` (a skipped pipeline takes its stages), and a `definition` with a pipeline's `label` and `displayOrder` or a stage's `label` and metadata field.
|
|
101
130
|
|
|
102
131
|
## Canonical form
|
|
103
132
|
|
|
104
|
-
`kalup fmt` validates, then rewrites `kalup.config.ts`, `hubspot/removed.ts`, every object file and the barrel: groups and properties sorted by internal name, tombstones by address, fields in a fixed order, options in display order, quotes as biome writes them, 120 columns. It keeps every value you wrote, `description: ''`, `options: []`, `false` and an empty `lifecycle` included: a present field is owned. `fmt --check` lists the files it would change and exits 2 when there are any. Old files go to `.kalup/history/<timestamp>/` first; the last 20 runs are kept.
|
|
133
|
+
`kalup fmt` validates, then rewrites `kalup.config.ts`, `hubspot/removed.ts`, every object and pipeline file and the barrel: groups and properties sorted by internal name, stages in the order written, tombstones by address, fields in a fixed order, options in display order, quotes as biome writes them, 120 columns. It keeps every value you wrote, `description: ''`, `options: []`, `false` and an empty `lifecycle` included: a present field is owned. `fmt --check` lists the files it would change and exits 2 when there are any. Old files go to `.kalup/history/<timestamp>/` first; the last 20 runs are kept.
|
package/docs/dictionary.md
CHANGED
|
@@ -10,8 +10,8 @@ This page is the reference. For the walk-through with examples, see [kalup docs]
|
|
|
10
10
|
## Layout
|
|
11
11
|
|
|
12
12
|
1. `# <project> data dictionary` and a line naming the source: the config files, or the target, portal ID and `observedAt` of the snapshot.
|
|
13
|
-
2. `## Coverage`. For a snapshot: whether the read was complete, the objects not read with the missing scope, objects absent from the portal, what `skip` overrides left out, unsupported properties (Kalup does not write them), schemas without a label, `name` overrides, config properties in a group no address can hold, counts out of scope and shadowed, custom objects config does not name, and the fields Kalup does not capture. Reference properties record only their options.
|
|
14
|
-
3. One `## <object>` section per object key, sorted: a custom object's labels, display property and property lists; a groups table (internal name, label); a properties table (internal name, label, type, field type, group, managed or reference, description);
|
|
13
|
+
2. `## Coverage`. For a snapshot: whether the read was complete, the objects not read with the missing scope, objects absent from the portal, what `skip` overrides left out, unsupported properties (Kalup does not write them), schemas without a label, `name` overrides, config properties in a group no address can hold, counts out of scope and shadowed, custom objects config does not name, objects whose pipelines were not read, and the fields Kalup does not capture. Reference properties record only their options.
|
|
14
|
+
3. One `## <object>` section per object key, sorted: a custom object's labels, display property and property lists; a groups table (internal name, label); a properties table (internal name, label, type, field type, group, managed or reference, description); one options table per enumeration (value, label, hidden, description), in display order; and per pipeline, sorted by ID, a `### Pipeline <label> (<id>)` heading, its display order, and a stages table in stage order (stage ID, label, and its probability or state; config adds the key). Config adds the key, codec, required and alias columns. Unsupported properties appear only under Coverage.
|
|
15
15
|
4. Config with `definition` overrides: `## Per-target overrides`, one row per address, field and target with its value, sorted.
|
|
16
16
|
|
|
17
17
|
## Escaping
|
|
@@ -20,7 +20,7 @@ Every string from a file or a portal is shown as text: newlines become spaces, c
|
|
|
20
20
|
|
|
21
21
|
## Deterministic
|
|
22
22
|
|
|
23
|
-
The same source gives the same bytes: objects, groups and
|
|
23
|
+
The same source gives the same bytes: objects, groups, properties and pipelines sorted, options and stages in display order, and no timestamp but a snapshot's own `observedAt`. Commit the dictionary and check it in CI:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
26
|
npx --no-install kalup docs --out DATA-DICTIONARY.md
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# E_DUPLICATE_LABEL
|
|
2
|
+
|
|
3
|
+
Two stages of one pipeline, or two pipelines of one object, share a label. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
HubSpot refuses a stage whose label another stage of the same pipeline has, ignoring case and spaces around it, and a pipeline whose label another pipeline of the same object has, ignoring case (live runs, 2026-10-05). The same label on stages of two pipelines, or on pipelines of two objects, is fine.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Give one of the two another label.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
stages: {
|
|
17
|
+
tasting: { id: 'orchard_tasting', label: 'Tasting', probability: 0.2 },
|
|
18
|
+
retasting: { id: 'orchard_retasting', label: 'tasting', probability: 0.3 },
|
|
19
|
+
},
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
hubspot/pipelines/deals.ts:9: E_DUPLICATE_LABEL: stages 'orchard_tasting' and 'orchard_retasting' of pipeline:deals/orchard_sales share the label 'tasting', ignoring case (fix: give one of the two another label) (docs: errors/E_DUPLICATE_LABEL.md)
|
|
24
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# E_MISSING_EXPORT
|
|
2
2
|
|
|
3
|
-
A file under `hubspot/` has no `defineObject` or `defineCustomObject` export. Exit 3.
|
|
3
|
+
A file under `hubspot/` has no `defineObject` or `defineCustomObject` export, or a file under `hubspot/pipelines/` no `definePipeline` export. Exit 3.
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Kalup reads every `.ts` file in the folder of object files (`hubspot/`, or the folder `dir` in `kalup.config.ts` names) except `index.ts` and `removed.ts` as an object file. A file with only imports, or an empty file, has nothing to read.
|
|
7
|
+
Kalup reads every `.ts` file in the folder of object files (`hubspot/`, or the folder `dir` in `kalup.config.ts` names) except `index.ts` and `removed.ts` as an object file, and each one under `pipelines/` as a pipeline file. A file with only imports, or an empty file, has nothing to read.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# E_PIPELINE_FIELD
|
|
2
|
+
|
|
3
|
+
A pipeline or stage states a field HubSpot would refuse, or drop without a word. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
A stage carries one metadata field, by its pipeline's object: `probability` on deals, from 0 to 1 and required, `ticketState` on tickets and `state` on custom objects, each `'OPEN'` or `'CLOSED'`. HubSpot drops any other metadata without an error, so a write would change nothing and every plan would show it again; HubSpot derives `isClosed` itself. A pipeline on another object, such as the contacts lifecycle pipeline, is read and compared, never written, so its stages carry no metadata. A pipeline's `displayOrder` is an integer from 0 up (live runs, 2026-10-01 and 2026-10-05).
|
|
8
|
+
|
|
9
|
+
A target's definition override that breaks one of these rules is `E_OVERRIDE_DEFINITION`.
|
|
10
|
+
|
|
11
|
+
## Fix
|
|
12
|
+
|
|
13
|
+
Change or remove the field the message names.
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
won: { id: 'orchard_signed', label: 'Signed', ticketState: 'CLOSED' },
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
hubspot/pipelines/deals.ts:10: E_PIPELINE_FIELD: ticketState is for ticket stages; a deal stage takes probability (fix: replace ticketState with probability, from 0 to 1) (docs: errors/E_PIPELINE_FIELD.md)
|
|
23
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# E_PIPELINE_ID
|
|
2
|
+
|
|
3
|
+
A pipeline or stage ID cannot be used: it forms no address, is too long, or another one holds it. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
A pipeline or stage ID is its address and what HubSpot stores, so it must hold no whitespace or slash. HubSpot stores a pipeline ID of at most 36 characters and a stage ID of at most 100, and answers 500 to a longer one. A pipeline ID is unique across the portal, deals and tickets included, and a stage ID across the pipelines of one object (live runs, 2026-10-01 and 2026-10-05), so two in config may not share one.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Give the pipeline or stage another ID. An ID is permanent once HubSpot creates it, so choose a short, readable one, such as the pipeline ID followed by the stage, `orchard_signed`.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
hubspot/pipelines/tickets.ts:5: E_PIPELINE_ID: pipeline:tickets/orchard_sales has the ID of pipeline:deals/orchard_sales, and HubSpot keeps pipeline IDs unique across objects (fix: give one of the two another ID) (docs: errors/E_PIPELINE_ID.md)
|
|
17
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# E_PIPELINE_STAGES
|
|
2
|
+
|
|
3
|
+
A pipeline has no stage, or a ticket pipeline has no closed stage. Exit 3.
|
|
4
|
+
|
|
5
|
+
## When
|
|
6
|
+
|
|
7
|
+
HubSpot refuses a pipeline with no stage, and a ticket pipeline with no stage whose `ticketState` is `'CLOSED'` (live runs, 2026-10-05). `kalup rm` refuses to remove such a pipeline's last stage, or its last closed one, for the same reason.
|
|
8
|
+
|
|
9
|
+
## Fix
|
|
10
|
+
|
|
11
|
+
Add a stage, or mark one ticket stage `ticketState: 'CLOSED'`. To drop the whole pipeline, run `kalup rm` on the pipeline.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
hubspot/pipelines/tickets.ts:3: E_PIPELINE_STAGES: pipeline:tickets/orchard_desk has no stage with ticketState 'CLOSED', and HubSpot needs one (fix: mark the stage tickets end in ticketState: 'CLOSED') (docs: errors/E_PIPELINE_STAGES.md)
|
|
17
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# E_TOMBSTONE_ADDRESS
|
|
2
2
|
|
|
3
|
-
A key in `hubspot/removed.ts`, or the address given to `kalup rm`, is not the address of a property or
|
|
3
|
+
A key in `hubspot/removed.ts`, or the address given to `kalup rm`, is not the address of a property, group, pipeline or stage. Exit 3.
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Each key in `hubspot/removed.ts` is an address, such as `property:companies/legacy_score`: the type, a colon, the object, a slash and the name. A key with no object, such as `property:legacy_score`, names nothing and is refused. This version removes properties
|
|
7
|
+
Each key in `hubspot/removed.ts` is an address, such as `property:companies/legacy_score`: the type, a colon, the object, a slash and the name. A key with no object, such as `property:legacy_score`, names nothing and is refused. A stage address names its pipeline as well: `stage:deals/renewals/won`. This version removes properties, property groups, pipelines and stages only, so a key of another type, such as `object:parcels`, is refused as well.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -19,5 +19,5 @@ export default defineRemoved({
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
hubspot/removed.ts:4: E_TOMBSTONE_ADDRESS: 'legacyScore' is not an address (fix: write the address of a property or
|
|
22
|
+
hubspot/removed.ts:4: E_TOMBSTONE_ADDRESS: 'legacyScore' is not an address (fix: write the address of a property, group, pipeline or stage, such as 'property:companies/legacy_score') (docs: errors/E_TOMBSTONE_ADDRESS.md)
|
|
23
23
|
```
|
|
@@ -4,14 +4,14 @@ A file under `hubspot/` that this version does not read. Exit 3.
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A `definePipeline` file outside `hubspot/pipelines/`, a `defineConfig` file under `hubspot/`, and a `defineRemoved` file anywhere under `hubspot/` except `hubspot/removed.ts`. With `dir` set in `kalup.config.ts`, the same paths under that folder. Kalup reports them instead of skipping them silently.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
11
|
-
Move
|
|
11
|
+
Move pipelines to `hubspot/pipelines/<object>.ts`, such as `hubspot/pipelines/deals.ts`. A `defineConfig` file belongs at the project root as `kalup.config.ts`, and tombstones belong in `hubspot/removed.ts`.
|
|
12
12
|
|
|
13
13
|
## Example
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
hubspot/
|
|
16
|
+
hubspot/deals.ts:1: E_UNSUPPORTED_FILE: a definePipeline file belongs under hubspot/pipelines/ (fix: move it to hubspot/pipelines/) (docs: errors/E_UNSUPPORTED_FILE.md)
|
|
17
17
|
```
|
|
@@ -4,7 +4,7 @@ Kalup refused to send a request to a write path through a read client. Exit 1. N
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
Every command that only reads (`pull`, `plan`, `status`, `compare`, `snapshot`) goes through a client that allows only paths tagged `read`, so none of them can reach a write path. Only `kalup apply` opens a write client, and it may send only the
|
|
7
|
+
Every command that only reads (`pull`, `plan`, `status`, `compare`, `snapshot`) goes through a client that allows only paths tagged `read`, so none of them can reach a write path. Only `kalup apply` opens a write client, and it may send only the writes on its own list (see `E_WRITE_NOT_ALLOWED`).
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ Kalup refused to send a write that this run may not send. Exit 1. Nothing was se
|
|
|
4
4
|
|
|
5
5
|
## When
|
|
6
6
|
|
|
7
|
-
A run that writes gets an explicit list of the writes it may send. This version allows creating, updating and archiving properties and property groups, and nothing else: no custom object schema writes. A request to any other write path, or to a read path through the write channel, is refused before it leaves Kalup.
|
|
7
|
+
A run that writes gets an explicit list of the writes it may send. This version allows creating, updating and archiving properties and property groups, and creating, updating and deleting pipelines and stages, and nothing else: no custom object schema writes and no pipeline replace (PUT). A request to any other write path, or to a read path through the write channel, is refused before it leaves Kalup.
|
|
8
8
|
|
|
9
9
|
## Fix
|
|
10
10
|
|
package/docs/plan.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Plan
|
|
2
2
|
|
|
3
|
-
`kalup plan [--target <name>] [--take config <address[#unit]>] [--out [<file>]] [--exit-code]` shows what apply would do to one target: a step per object, group and
|
|
3
|
+
`kalup plan [--target <name>] [--take config <address[#unit]>] [--out [<file>]] [--exit-code]` shows what apply would do to one target: a step per object, group, property, pipeline and stage config manages, `definition` overrides applied (config.md), then the releases and deletes tombstones ask for. It writes neither portal nor state. `--out <file>` saves the plan/1 document; `--out` alone saves it as `.kalup/plans/<target>-<planId>.json` and prints the path. `kalup apply <file>` applies either.
|
|
4
4
|
|
|
5
5
|
This page is the reference. For the walk-through with examples, see [kalup plan](https://kalup.dev/docs/commands/plan) and [Drift](https://kalup.dev/docs/concepts/drift) on the website.
|
|
6
6
|
|
|
@@ -9,8 +9,8 @@ This page is the reference. For the walk-through with examples, see [kalup plan]
|
|
|
9
9
|
1. Validate (`E_NO_CONFIG` exit 1, other issues exit 3), then pick the target (targets.md).
|
|
10
10
|
2. The read key, then the portal guard (`E_TARGET_PORTAL_MISMATCH`, exit 4).
|
|
11
11
|
3. State for that portal, `.kalup/state/portal-<portalId>.json`; an unusable file is `E_STATE_INVALID`.
|
|
12
|
-
4. Pull's read and scope, plus tombstoned properties. A 403 leaves that object unread (`E_SCOPE`).
|
|
13
|
-
5. Limits Tracking (403 without a `crm.objects.*` scope; `W_LIMIT_UNREADABLE` for property creates), then the three `archived=true` lists of each object with a property create or an owned property HubSpot no longer holds. A group delete reads no archived list: only active properties block it. A 403 there is exit 1.
|
|
12
|
+
4. Pull's read and scope, plus tombstoned properties, and the pipelines of each object whose files define one, with `pipelines: true`, or with a tombstoned pipeline or stage. A 403 leaves that object unread (`E_SCOPE`); on the pipelines list, only its pipelines.
|
|
13
|
+
5. Limits Tracking (403 without a `crm.objects.*` scope; `W_LIMIT_UNREADABLE` for property and pipeline creates), then the three `archived=true` lists of each object with a property create or an owned property HubSpot no longer holds. A group delete reads no archived list: only active properties block it. A 403 there is exit 1.
|
|
14
14
|
6. The plan, checked against `plan-1.schema.json` (`E_PLAN_SCHEMA` is a bug).
|
|
15
15
|
|
|
16
16
|
## State and the base
|
|
@@ -25,7 +25,7 @@ A state entry owns an address when it was created or adopted and its `id` is the
|
|
|
25
25
|
| `pulled` | present | `adopt` against the base pull recorded: a file edit since is a `config-change` |
|
|
26
26
|
| owned | absent | no step; listed in `missing` |
|
|
27
27
|
|
|
28
|
-
Each unit (a field, an option, an option's `label`, `hidden` and `description`, `options.order`) is classified against the base: `config-change` is written; `drift` (only HubSpot moved), `conflict` (both moved) and `diverged` (no base) are held. An option in config and the base that HubSpot dropped is drift; one config dropped is kept with a note. `removedOptions` and `options: 'exact'` remove, risk `risky`. Under takeover, `options` defaults to `exact`: such a removal is `destructive`, labelled `takeover`, and blocked without `allowDestroy` (`policy`) or after an incomplete read (`scope`).
|
|
28
|
+
Each unit (a field, an option, an option's `label`, `hidden` and `description`, `options.order`, a pipeline's `stages` order) is classified against the base: `config-change` is written; `drift` (only HubSpot moved), `conflict` (both moved) and `diverged` (no base) are held. An option in config and the base that HubSpot dropped is drift; one config dropped is kept with a note. `removedOptions` and `options: 'exact'` remove, risk `risky`. Under takeover, `options` defaults to `exact`: such a removal is `destructive`, labelled `takeover`, and blocked without `allowDestroy` (`policy`) or after an incomplete read (`scope`).
|
|
29
29
|
|
|
30
30
|
Converged units with a missing or outdated base go in `baseUnits`: apply records them without a write. An update that only holds or notes units is never applied.
|
|
31
31
|
|
|
@@ -41,17 +41,30 @@ With `drift: 'overwrite'`, drift and conflicts are written labelled `reverts-ui-
|
|
|
41
41
|
|
|
42
42
|
The first rule that matches: a `skip` override (no step, `coverage.excluded`); a `lookup` override; an unread object (`scope`, action `unknown`); a blocked parent or missing group (`dependency-blocked`); a property Kalup does not write (pull.md), whose fix makes it a `p.string` reference; for a create, a missing `name` override target, an archived property name (a create restores it; an archived group's name is created anew), or no limit room; HubSpot-defined or calculated, a `type` or `hasUniqueValue` difference, or read-only definition or options. A custom object schema is compared and never written: a missing one is blocked, and its differences are held or noted.
|
|
43
43
|
|
|
44
|
+
## Pipelines and stages
|
|
45
|
+
|
|
46
|
+
- Kalup writes the pipelines of deals, tickets and custom objects. Those of contacts, companies, appointments, services, listings, courses, orders and leads are compared, never written: a create is blocked `unsupported`, and a unit a step would write becomes a note.
|
|
47
|
+
- A pipeline create carries every config stage of the pipeline in one request, since HubSpot refuses a pipeline with no stage. The step lists them under `stages`; they get no steps of their own.
|
|
48
|
+
- A stage create in an existing pipeline goes after the last stage. When config places it before a stage HubSpot holds, the pipeline's step also sets the `stages` order; apply moves the stages one request at a time after the stage steps, so the budget counts two calls per stage of that order.
|
|
49
|
+
- Risk: a create is `safe`, unless its pipeline or stage ID is all digits, an ID HubSpot assigned in another portal: `risky`, with a note naming the `name` override and the portal's pipeline with the same label. A label, `displayOrder` or order change is `safe`. A change of `probability`, `ticketState` or `state` is `risky`: it changes how existing records count in forecasts and in open and closed reports. A delete is `destructive`.
|
|
50
|
+
- Blocked `unsupported`: a create whose pipeline ID another object's pipeline holds, or whose stage ID another pipeline of the object holds, naming the holder; a stage delete that would leave its pipeline with no stage, or a ticket pipeline with no closed stage. Blocked `scope`: a pipeline or stage of an object whose pipelines were not read or answered 403.
|
|
51
|
+
- The first pipeline created on a custom object carries a note: HubSpot adds its own properties `hs_pipeline` and `hs_pipeline_stage` to the object, for good. A deal or ticket pipeline create notes when the plan did not read the other object's pipelines: HubSpot keeps pipeline IDs unique across the two.
|
|
52
|
+
- A pipeline create is blocked `override` when the target skips every one of its stages. A stage tombstone that asks otherwise than its pipeline's is blocked with the reason. A destroy on a pipeline or stage Kalup does not write is blocked, with the release fix.
|
|
53
|
+
- A stage order shows by label in the plan text (`stage order: "Tasting", "Signed" -> ...`). The plan document keeps the IDs, and `stageLabels` on the step maps each to its label; it is display only and not approved.
|
|
54
|
+
- Takeover never deletes a pipeline or stage.
|
|
55
|
+
|
|
44
56
|
## Tombstones, missing and orphans
|
|
45
57
|
|
|
46
|
-
`hubspot/removed.ts` tombstones name properties and
|
|
58
|
+
`hubspot/removed.ts` tombstones name properties, groups, pipelines and stages:
|
|
47
59
|
|
|
48
60
|
- `release`: a `release` step drops the entry, even one naming another portal name; nothing is sent.
|
|
49
61
|
- `destroy`, present: a `delete`, risk `destructive`, labelled `existed-before-kalup` for an adopted resource, expecting every base unit's live value. Blocked with `policy` without `allowDestroy: true`, `unsupported` when it is not archivable or a group still holds active properties the plan does not delete (archived ones do not block: HubSpot archives a group once every property in it is archived), `not-owned` without an owning entry.
|
|
50
62
|
- `destroy`, absent by a complete read: a release expecting `exists: false`.
|
|
63
|
+
- A pipeline's tombstone covers its stages: they get no steps and no orphan notes. A pipeline or stage delete is permanent: HubSpot keeps no archive. HubSpot refuses it while a record sits in the stage, and apply names the stages and records.
|
|
51
64
|
|
|
52
65
|
Under takeover (config.md), a `delete` labelled `takeover` archives each custom property and group in the pull scope that config lacks, with a `mode` note naming the statement that asked for it; an option removal takeover asks for carries the note too. One `Takeover on <objects>` heading precedes the first such step and says whether each is confirmed at a terminal or all are blocked. Blocked with `policy` without `allowDestroy`, `scope` after an incomplete read, `unsupported` when not archivable or a group keeps an active property. The `policy` fix leads with `kalup pull --target <t> --only <address>`, which keeps it in config, then `exclude` or `lifecycle: { options: 'additive' }` to leave it unmanaged, then `allowDestroy`. A delete expects every captured field's live value. Apply checks the same rules against its own read (a skipped group, a schema's properties, an empty group).
|
|
53
66
|
|
|
54
|
-
Releases follow the config steps, then deletes, the tombstones' and then takeover's, properties before groups.
|
|
67
|
+
Releases follow the config steps, then deletes, the tombstones' and then takeover's, properties before groups, then stages, then pipelines.
|
|
55
68
|
|
|
56
69
|
`missing` lists owned resources a complete read did not find, with `archived` (`null` for a group) and the exits; `orphans`, owned entries config no longer names, with both `kalup rm` commands; one naming another portal name, only `--release`.
|
|
57
70
|
|
|
@@ -61,7 +74,7 @@ Releases follow the config steps, then deletes, the tombstones' and then takeove
|
|
|
61
74
|
|
|
62
75
|
`writesHash` digests the target, portal, policy, state lineage and serial, `normVersions`, bindings, and each unblocked step with an effect: `address`, `action`, `transport`, `api`, `labels`, `baseUnits`, `desired`, `ignoreChanges`, `changes` (`unit`, `op`, `after`), `expect`. Titles, held values and notes stay out. `planId` is `pl_` plus its first 12 hex digits.
|
|
63
76
|
|
|
64
|
-
A write's `expect` holds the live value of each field it sets, the full options when any option changes,
|
|
77
|
+
A write's `expect` holds the live value of each field it sets, the full options when any option changes, a property's `type` and `fieldType`, and a pipeline's live stage order when the step sets it. A pipeline delete expects the full live stage list, so a stage added in HubSpot since the review stops it.
|
|
65
78
|
|
|
66
79
|
## Output
|
|
67
80
|
|