@intentius/chant-lexicon-terraform 0.124.45 → 0.124.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/__fixtures__/capabilities/chant.config.d.ts +26 -0
  2. package/dist/__fixtures__/capabilities/chant.config.d.ts.map +1 -0
  3. package/dist/__fixtures__/capabilities/chant.config.js +13 -0
  4. package/dist/__fixtures__/effective-topology/chant.config.d.ts +13 -0
  5. package/dist/__fixtures__/effective-topology/chant.config.d.ts.map +1 -0
  6. package/dist/__fixtures__/effective-topology/chant.config.js +9 -0
  7. package/dist/__fixtures__/kind-type/chant.config.d.ts +13 -0
  8. package/dist/__fixtures__/kind-type/chant.config.d.ts.map +1 -0
  9. package/dist/__fixtures__/kind-type/chant.config.js +9 -0
  10. package/dist/__fixtures__/search-roots/chant.config.d.ts +13 -0
  11. package/dist/__fixtures__/search-roots/chant.config.d.ts.map +1 -0
  12. package/dist/__fixtures__/search-roots/chant.config.js +9 -0
  13. package/dist/__fixtures__/sensitive-default/chant.config.d.ts +13 -0
  14. package/dist/__fixtures__/sensitive-default/chant.config.d.ts.map +1 -0
  15. package/dist/__fixtures__/sensitive-default/chant.config.js +9 -0
  16. package/dist/capabilities/adapter.d.ts +9 -0
  17. package/dist/capabilities/adapter.d.ts.map +1 -0
  18. package/dist/capabilities/adapter.js +25 -0
  19. package/dist/capabilities/mapping.json +166 -0
  20. package/dist/config.d.ts +52 -0
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +23 -0
  23. package/dist/describe-resources.d.ts +101 -8
  24. package/dist/describe-resources.d.ts.map +1 -1
  25. package/dist/describe-resources.js +395 -56
  26. package/dist/hcl/parse.d.ts +10 -0
  27. package/dist/hcl/parse.d.ts.map +1 -1
  28. package/dist/hcl/parse.js +20 -2
  29. package/dist/hcl/redact.d.ts +26 -0
  30. package/dist/hcl/redact.d.ts.map +1 -0
  31. package/dist/hcl/redact.js +228 -0
  32. package/dist/integrity.json +2 -2
  33. package/dist/manifest.json +1 -1
  34. package/dist/op/activities/terraform.d.ts +109 -4
  35. package/dist/op/activities/terraform.d.ts.map +1 -1
  36. package/dist/op/activities/terraform.js +148 -7
  37. package/dist/plan-observation.d.ts +153 -0
  38. package/dist/plan-observation.d.ts.map +1 -0
  39. package/dist/plan-observation.js +354 -0
  40. package/dist/plugin.d.ts.map +1 -1
  41. package/dist/plugin.js +2 -0
  42. package/package.json +2 -2
  43. package/src/__fixtures__/ACCEPTANCE.md +53 -1
  44. package/src/__fixtures__/capabilities/catalog/main.tf +105 -0
  45. package/src/__fixtures__/capabilities/chant.config.ts +15 -0
  46. package/src/__fixtures__/capabilities/findings/main.tf +225 -0
  47. package/src/__fixtures__/capabilities/ledger/main.tf +265 -0
  48. package/src/__fixtures__/capabilities/package.json +1 -0
  49. package/src/__fixtures__/effective-topology/chant.config.ts +11 -0
  50. package/src/__fixtures__/effective-topology/net/main.tf +176 -0
  51. package/src/__fixtures__/effective-topology/package.json +6 -0
  52. package/src/__fixtures__/kind-type/app/main.tf +35 -0
  53. package/src/__fixtures__/kind-type/chant.config.ts +11 -0
  54. package/src/__fixtures__/kind-type/package.json +6 -0
  55. package/src/__fixtures__/live-plan-depth/README.md +59 -0
  56. package/src/__fixtures__/live-plan-depth/live-plan.json +110 -0
  57. package/src/__fixtures__/live-plan-depth/show-plan-sensitive.json +1 -0
  58. package/src/__fixtures__/live-plan-depth/show-plan.json +1 -0
  59. package/src/__fixtures__/plan-read/main.tf +46 -0
  60. package/src/__fixtures__/plan-read/next.tfvars +1 -0
  61. package/src/__fixtures__/plan-read-drift/README.md +33 -0
  62. package/src/__fixtures__/plan-read-drift/main.tf +63 -0
  63. package/src/__fixtures__/plan-read-drift/next.tfvars +1 -0
  64. package/src/__fixtures__/search-roots/chant.config.ts +11 -0
  65. package/src/__fixtures__/search-roots/net/main.tf +73 -0
  66. package/src/__fixtures__/search-roots/package.json +6 -0
  67. package/src/__fixtures__/sensitive-default/chant.config.ts +11 -0
  68. package/src/__fixtures__/sensitive-default/net/main.tf +38 -0
  69. package/src/__fixtures__/sensitive-default/package.json +6 -0
  70. package/src/__fixtures__/show-plan.json +663 -0
  71. package/src/capabilities/adapter.ts +32 -0
  72. package/src/capabilities/mapping.json +166 -0
  73. package/src/capabilities-workspace.e2e.test.ts +251 -0
  74. package/src/capabilities.test.ts +264 -0
  75. package/src/config.ts +26 -0
  76. package/src/describe-resources.live-plan.test.ts +302 -0
  77. package/src/describe-resources.live-root-unchanged.test.ts +126 -0
  78. package/src/describe-resources.plan.test.ts +437 -0
  79. package/src/describe-resources.ts +445 -52
  80. package/src/effective-topology.test.ts +134 -0
  81. package/src/hcl/parse.ts +30 -2
  82. package/src/hcl/redact.ts +181 -0
  83. package/src/live-plan-depth.acceptance.e2e.test.ts +193 -0
  84. package/src/op/activities/terraform.test.ts +118 -1
  85. package/src/op/activities/terraform.ts +213 -9
  86. package/src/plan-observation.ts +428 -0
  87. package/src/plan-read.acceptance.test.ts +184 -0
  88. package/src/plugin.ts +2 -0
  89. package/src/redact-ir.e2e.test.ts +79 -0
  90. package/src/redact.test.ts +111 -0
  91. package/src/search-build-roots.test.ts +125 -0
  92. package/src/search-count.test.ts +144 -0
  93. package/src/search-kind-type.test.ts +136 -0
@@ -39,7 +39,8 @@
39
39
  * {@link MIN_CHOUDOUFU_VERSION}.
40
40
  */
41
41
  import { exec } from "node:child_process";
42
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
42
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
43
+ import { tmpdir } from "node:os";
43
44
  import { promisify } from "node:util";
44
45
  import { resolve, dirname, join, relative, sep } from "node:path";
45
46
  import { loadChantConfigUpward } from "@intentius/chant/config";
@@ -59,7 +60,7 @@ const MAX_BUFFER = 64 * 1024 * 1024;
59
60
  export const DEFAULT_PLAN_FILE = "chant.tfplan";
60
61
  /** Binary used when the `terraform` namespace records no preference. */
61
62
  export const DEFAULT_TERRAFORM_BINARY = "terraform";
62
- /** Where {@link choudoufuLivePlan} writes GitHub issue #788's JSON document, relative to the root directory. */
63
+ /** Where the {@link choudoufuLivePlan} Op activity writes GitHub issue #788's JSON document, relative to the root directory. */
63
64
  export const DEFAULT_LIVE_PLAN_DOCUMENT_FILE = "chant.live-plan.json";
64
65
  /**
65
66
  * choudoufu below this refuses. v0.12.0 brought `live-plan -json`, `live-ls`
@@ -927,6 +928,133 @@ export async function terraformShow(args, signal) {
927
928
  ...(planFile ? countPlanChanges(json) : { adds: 0, changes: 0, destroys: 0 }),
928
929
  };
929
930
  }
931
+ // ── The plan read (#4358) ───────────────────────────────────────────────────
932
+ /** Prefix of the scratch directory a plan read writes its plan file into, under the OS temp directory. */
933
+ export const PLAN_READ_DIR_PREFIX = "chant-terraform-plan-";
934
+ /** The plan file's name inside that scratch directory. */
935
+ export const PLAN_READ_FILE = "plan.tfplan";
936
+ /**
937
+ * `terraform plan` for an observation read (#4358): `-input=false -lock=false
938
+ * -out=<file>`, plus the root's own `-var-file`s.
939
+ *
940
+ * `-lock=false` is the point. A read must never take the backend's state lock,
941
+ * or a teammate's apply that starts while chant is reading would fail on
942
+ * "Error acquiring the state lock". `-detailed-exitcode` is not passed: the
943
+ * read wants the document, not the boolean, so exit 0 is the only success.
944
+ * The `-out` path is always outside the root (see {@link terraformPlanRead}).
945
+ */
946
+ export function terraformPlanReadCommand(opts) {
947
+ const parts = [opts.binary, "plan", "-input=false", "-lock=false"];
948
+ for (const varFile of opts.varFiles ?? [])
949
+ parts.push(`-var-file=${quoteArg(varFile)}`);
950
+ parts.push(`-out=${quoteArg(opts.planFile)}`);
951
+ return parts.join(" ");
952
+ }
953
+ /**
954
+ * The one line a person runs to initialise a root chant refuses to plan,
955
+ * with the root's `backendConfig` carried, so it can be pasted as printed.
956
+ */
957
+ export function terraformInitLine(opts) {
958
+ const init = terraformInitCommand({
959
+ binary: opts.binary,
960
+ ...(opts.backendConfig ? { backendConfig: opts.backendConfig } : {}),
961
+ });
962
+ return init.replace(`${opts.binary} init`, `${opts.binary} -chdir=${quoteArg(opts.dir)} init`);
963
+ }
964
+ /** A plan read that did not produce a document. `kind` decides the fallback. */
965
+ export class TerraformPlanReadError extends Error {
966
+ constructor(kind, message, dir, initLine) {
967
+ super(message);
968
+ this.name = "TerraformPlanReadError";
969
+ this.kind = kind;
970
+ this.dir = dir;
971
+ if (initLine !== undefined)
972
+ this.initLine = initLine;
973
+ }
974
+ }
975
+ /**
976
+ * Sort a failed plan's own output into a {@link TerraformPlanReadFailureKind}.
977
+ * Matched on terraform's and OpenTofu's own error summaries. Order matters:
978
+ * an S3 backend without credentials says both "backend" and "credentials",
979
+ * and it is the backend that failed, so the backend patterns are read first.
980
+ */
981
+ export function classifyPlanReadFailure(output) {
982
+ if (/Backend initialization required|Inconsistent dependency lock file|Required plugins are not installed|Module not installed|Missing required provider|please run "(terraform|tofu|choudoufu) init"|Run "(terraform|tofu|choudoufu) init"|Backend configuration changed/i.test(output)) {
983
+ return "not-initialised";
984
+ }
985
+ if (/error loading state|Failed to load state|Error refreshing state|Failed to get existing workspaces|error configuring (the )?\S+ backend|Error: Failed to (read|retrieve) state|Error retrieving state|Error acquiring the state lock|state data in S3|NoSuchBucket|failed to (get|read) (remote )?state|Unable to access object|\bbackend\b.*(credential|denied|unauthori[sz]ed|forbidden)/i.test(output)) {
986
+ return "backend";
987
+ }
988
+ return "plan";
989
+ }
990
+ /**
991
+ * Whether a root has been initialised: `.terraform` (or `TF_DATA_DIR`, when
992
+ * the environment points elsewhere) exists. A root whose providers or modules
993
+ * have since changed passes this check and is caught by
994
+ * {@link classifyPlanReadFailure} on the plan's own error instead.
995
+ */
996
+ export function rootIsInitialised(dir, env = process.env) {
997
+ const dataDir = env.TF_DATA_DIR ? resolve(dir, env.TF_DATA_DIR) : join(dir, ".terraform");
998
+ return existsSync(dataDir);
999
+ }
1000
+ /**
1001
+ * Read a saved plan for one root without writing anything in it (#4358):
1002
+ * `plan -input=false -lock=false -out=<tmp>/plan.tfplan` in the root, then
1003
+ * `show -json <tmp>/plan.tfplan`, then remove `<tmp>`.
1004
+ *
1005
+ * Three rules, each a lesson behold already learned reading choudoufu
1006
+ * members (behold `src/choudoufu-plan.ts`):
1007
+ *
1008
+ * - No lock. `-lock=false` on the plan, always ({@link terraformPlanReadCommand}).
1009
+ * - Nothing in the root. `<tmp>` is a fresh `mkdtemp` under the OS temp
1010
+ * directory and is removed in a `finally`, failure or not. `init` is never
1011
+ * run: an uninitialised root throws a {@link TerraformPlanReadError} of
1012
+ * kind `"not-initialised"` carrying the line to run.
1013
+ * - A failure says which kind it is, so the observation can fall back by
1014
+ * depth ({@link classifyPlanReadFailure}).
1015
+ *
1016
+ * Uses the longInfra profile, like {@link terraformPlan}: a plan refreshes
1017
+ * every resource against its provider.
1018
+ */
1019
+ export async function terraformPlanRead(args, signal) {
1020
+ const { binary, root, dir } = await resolveRoot(args, signal);
1021
+ const env = terraformEnvironment(root);
1022
+ const initLine = terraformInitLine({ binary, dir, ...(root.backendConfig ? { backendConfig: root.backendConfig } : {}) });
1023
+ const notInitialised = (detail) => new TerraformPlanReadError("not-initialised", `terraform.roots.${args.root} (${dir}) ${detail}: run \`${initLine}\` first. chant never runs init on a read, since init writes into the root.`, dir, initLine);
1024
+ if (!rootIsInitialised(dir, { ...process.env, ...env }))
1025
+ throw notInitialised("has never been initialised");
1026
+ const planDir = mkdtempSync(join(tmpdir(), PLAN_READ_DIR_PREFIX));
1027
+ const planFile = join(planDir, PLAN_READ_FILE);
1028
+ const command = terraformPlanReadCommand({ binary, planFile, ...(root.varFiles ? { varFiles: root.varFiles } : {}) });
1029
+ try {
1030
+ try {
1031
+ const { stderr } = await run(command, dir, env, signal);
1032
+ // The plan's own stdout is the human render, which this read never
1033
+ // uses; stderr carries warnings worth a human's eye.
1034
+ if (stderr)
1035
+ console.error(stderr);
1036
+ }
1037
+ catch (err) {
1038
+ const failure = err;
1039
+ if (typeof failure.code !== "number")
1040
+ throw err;
1041
+ const output = `${failure.stderr ?? ""}\n${failure.stdout ?? ""}`.replace(/\x1b\[[0-9;]*m/g, "");
1042
+ const kind = classifyPlanReadFailure(output);
1043
+ const line = output
1044
+ .split("\n")
1045
+ .map((l) => l.replace(/^[\s│╷╵]+/, "").trim())
1046
+ .find((l) => /^Error:/.test(l)) ?? `exit ${failure.code}`;
1047
+ if (kind === "not-initialised")
1048
+ throw notInitialised(`needs init (${line})`);
1049
+ throw new TerraformPlanReadError(kind, `${binary} plan failed in ${dir}: ${line}`, dir);
1050
+ }
1051
+ const shown = await run(terraformShowCommand({ binary, json: true, planFile }), dir, env, signal);
1052
+ return { json: JSON.parse(shown.stdout), dir, planDir, command };
1053
+ }
1054
+ finally {
1055
+ rmSync(planDir, { recursive: true, force: true });
1056
+ }
1057
+ }
930
1058
  /**
931
1059
  * The estate a choudoufu-only activity runs against, in precedence order:
932
1060
  * `args.estate` when the step names one, then the root's own `estate`
@@ -1003,14 +1131,16 @@ export function liveDocumentFrom(stdout) {
1003
1131
  * the root's environment. The graph command runs in such a scope, so its
1004
1132
  * observation and its prediction see one `live-plan` between them, and the
1005
1133
  * graph's statuses and the prediction's figures are statements about the
1006
- * same read. The document file is still written on every call.
1134
+ * same read. The document is returned, not written: the root is left
1135
+ * byte-for-byte as it was (#4379). A caller that wants the file passes
1136
+ * `documentPath` (relative to the root) and it is written there on request.
1007
1137
  */
1008
1138
  export async function choudoufuLivePlanDocument(args, signal) {
1009
1139
  const resolved = await resolveRoot(args, signal);
1010
1140
  const { binary, dir } = resolved;
1011
1141
  const estate = resolveEstate(args, resolved, "choudoufuLivePlan");
1012
1142
  const env = terraformEnvironment(resolved.root);
1013
- const documentPath = args.documentPath ?? DEFAULT_LIVE_PLAN_DOCUMENT_FILE;
1143
+ const documentPath = args.documentPath;
1014
1144
  // A root that declares its own estate is run without `-estate`: choudoufu
1015
1145
  // refuses the flag beside a declaration and, since v0.14.0, settles the name
1016
1146
  // from the declaration itself. See {@link choudoufuLivePlanCommand}.
@@ -1049,11 +1179,22 @@ export async function choudoufuLivePlanDocument(args, signal) {
1049
1179
  // the identity on a supported binary. See {@link liveDocumentFrom}.
1050
1180
  const document = liveDocumentFrom(jsonStdout);
1051
1181
  const json = JSON.parse(document);
1052
- writeFileSync(join(dir, documentPath), document);
1053
- return { drift, json, dir, documentPath, estate, ...countLivePlanUnowned(json) };
1182
+ if (documentPath !== undefined)
1183
+ writeFileSync(join(dir, documentPath), document);
1184
+ return {
1185
+ drift,
1186
+ json,
1187
+ dir,
1188
+ ...(documentPath === undefined ? {} : { documentPath }),
1189
+ estate,
1190
+ ...countLivePlanUnowned(json),
1191
+ };
1054
1192
  }
1055
1193
  export async function choudoufuLivePlan(args, signal) {
1056
- const { drift, json, dir, documentPath, estate, ...unowned } = await choudoufuLivePlanDocument(args, signal);
1194
+ // The Op activity is the one caller that wants the file: `documentPath` is a
1195
+ // declared `.out` ref a later step or reviewer opens (#2103), so it asks.
1196
+ const documentPath = args.documentPath ?? DEFAULT_LIVE_PLAN_DOCUMENT_FILE;
1197
+ const { drift, json, dir, estate, ...unowned } = await choudoufuLivePlanDocument({ ...args, documentPath }, signal);
1057
1198
  const resolved = await resolveRoot(args, signal);
1058
1199
  const { binary } = resolved;
1059
1200
  const env = terraformEnvironment(resolved.root);
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The plan depth's projection (#4358): a saved plan's `show -json` document,
3
+ * turned into the attributes an observed terraform block carries.
4
+ *
5
+ * The state read (`terraform show -json` over state) says what the last apply
6
+ * recorded. A plan says what the next apply would do, and four facts exist
7
+ * only there:
8
+ *
9
+ * | plan document | IR attribute |
10
+ * |---|---|
11
+ * | `resource_changes[].change.actions` | `planAction`, `planActions` |
12
+ * | `resource_changes[].change.replace_paths`, `resource_changes[].action_reason` | `planReplacePaths`, `planActionReason` |
13
+ * | `resource_changes[].change.after_unknown` | `planAfterUnknown` |
14
+ * | `resource_drift[]`, `relevant_attributes[]` | `planDrift`, `planDriftAction`, `planRelevantAttributes` |
15
+ *
16
+ * plus `planChanges`, the attribute-level before and after of each planned
17
+ * update or replace. `PLAN_ATTRIBUTE_NAMES` lists them for a consumer.
18
+ *
19
+ * ## Paths
20
+ *
21
+ * Every attribute path is one string: object keys joined with `.`, list
22
+ * indexes and keys that are not identifiers in brackets, so
23
+ * `lifecycle_commands[0].read`, `tags["kubernetes.io/role"]`. A `chant search
24
+ * attr:planReplacePaths=triggers_replace` term matches by substring over that.
25
+ *
26
+ * ## Sensitive values never leave this module
27
+ *
28
+ * `before_sensitive` and `after_sensitive` are trees parallel to `before` and
29
+ * `after`: `true` at a node marks that node and everything under it. A value
30
+ * under a `true` node is replaced with `(sensitive)` before it is put on an
31
+ * attribute, at any depth, so a sensitive value nested in a map is caught
32
+ * exactly like a sensitive top-level attribute. Only leaves are ever emitted,
33
+ * never a whole object, so no unwalked subtree can carry a secret past the
34
+ * check.
35
+ *
36
+ * A flag on either side redacts both sides of that path. On a stock root the
37
+ * two sides are flagged alike, because state records which paths were
38
+ * sensitive. On a choudoufu live root (#4371) they are not: prior state is
39
+ * rebuilt from the account on every run and carries no marks from the
40
+ * configuration, so an attribute made sensitive by a `sensitive` variable is
41
+ * flagged on `after` only, while `before` holds the live value, which is the
42
+ * previous secret. Recorded in `__fixtures__/live-plan-depth/`.
43
+ *
44
+ * Flags alone are not enough, as a real plan shows: `terraform_data` copies
45
+ * `input` to `output`, and the plan flags `input.token` sensitive while
46
+ * leaving `output.token` unflagged with the same value in it. So every string
47
+ * found under a sensitive flag anywhere in the document is collected first,
48
+ * and any emitted value equal to one of them is redacted too. That catches a
49
+ * copy; it cannot catch a secret a provider transformed (hashed, embedded in a
50
+ * longer string), and the observation page says so.
51
+ *
52
+ * Nothing from `prior_state` is surfaced but each row's `id`, which is the
53
+ * state read's rule too.
54
+ */
55
+ /** What stands in for a value flagged sensitive, or equal to one that was. */
56
+ export declare const SENSITIVE_VALUE = "(sensitive)";
57
+ /** OpenTofu's and Terraform's own phrase for a value the plan cannot know yet. */
58
+ export declare const KNOWN_AFTER_APPLY = "(known after apply)";
59
+ /**
60
+ * Every attribute the plan depth adds to an observed block, beside the state
61
+ * read's `address`, `root`, `resourceType`, `mode` and `provider`. A block the
62
+ * plan does not touch carries `planAction: "no-op"` and none of the optional
63
+ * ones.
64
+ */
65
+ export declare const PLAN_ATTRIBUTE_NAMES: readonly ["planAction", "planActions", "planActionReason", "planReplacePaths", "planAfterUnknown", "planChanges", "planDrift", "planDriftAction", "planRelevantAttributes"];
66
+ /** One attribute that differs between two sides of a change, at leaf level. */
67
+ export interface PlanValueChange {
68
+ /** Attribute path, see the module doc. */
69
+ path: string;
70
+ /** Before value, or `(sensitive)`. `null` where the side had no value. */
71
+ before: unknown;
72
+ /** After value, `(sensitive)`, or `(known after apply)`. */
73
+ after: unknown;
74
+ /** The instance this change belongs to, when the block has more than one. */
75
+ instance?: string;
76
+ /** On a drift entry: the plan reads this attribute (`relevant_attributes`). */
77
+ relevant?: boolean;
78
+ }
79
+ /** One `resource_changes[]` entry, projected. */
80
+ export interface PlanInstanceChange {
81
+ address: string;
82
+ actions: string[];
83
+ actionReason?: string;
84
+ replacePaths: string[];
85
+ afterUnknown: string[];
86
+ changes: PlanValueChange[];
87
+ }
88
+ /** One `resource_drift[]` entry, projected. */
89
+ export interface PlanInstanceDrift {
90
+ address: string;
91
+ actions: string[];
92
+ changes: PlanValueChange[];
93
+ }
94
+ /** A plan document, indexed for the plan depth. */
95
+ export interface PlanIndex {
96
+ /**
97
+ * `prior_state`, as the document carries it. The reader indexes it exactly
98
+ * as the state read indexes state, so presence and ownership come from here.
99
+ */
100
+ priorState: unknown;
101
+ /** `resource_changes[]`, by instance address. */
102
+ changes: Map<string, PlanInstanceChange>;
103
+ /** `resource_drift[]`, by instance address. */
104
+ drift: Map<string, PlanInstanceDrift>;
105
+ /** `relevant_attributes[]`, by instance address: the attribute paths the plan read. */
106
+ relevant: Map<string, string[]>;
107
+ }
108
+ /** Render one path segment list as a single string. */
109
+ export declare function renderPath(segments: ReadonlyArray<string | number>): string;
110
+ /**
111
+ * The leaf-level differences between two sides of one change. Exported for
112
+ * the redaction tests; see the module doc for the rules.
113
+ */
114
+ export declare function diffChange(change: {
115
+ before?: unknown;
116
+ after?: unknown;
117
+ before_sensitive?: unknown;
118
+ after_sensitive?: unknown;
119
+ after_unknown?: unknown;
120
+ }, secrets: ReadonlySet<string>): PlanValueChange[];
121
+ /**
122
+ * Index a `show -json <planfile>` document. Unknown fields are ignored and a
123
+ * missing section reads as empty, the same tolerance the state read has.
124
+ */
125
+ export declare function indexPlan(document: unknown): PlanIndex;
126
+ /**
127
+ * The block address an instance address belongs to: every instance key
128
+ * removed, on the resource and on each module call, so `module.a[0].x.y["k"]`
129
+ * belongs to `module.a.x.y`.
130
+ */
131
+ export declare function blockAddressOf(instance: string): string;
132
+ /** One action list as one word: `replace` for either order of delete and create. */
133
+ export declare function actionWord(actions: readonly string[]): string;
134
+ /**
135
+ * The plan attributes of one declared block, aggregated over its instances.
136
+ * `instances` are the block's instance addresses in `prior_state`; a block in
137
+ * the plan that has none there is a planned create, which the reader reports
138
+ * absent and annotates with {@link plannedAttributesFor} instead.
139
+ */
140
+ export declare function planAttributesFor(address: string, index: PlanIndex): Record<string, unknown>;
141
+ /**
142
+ * The plan attributes of a block that has no row in `prior_state` (#4372),
143
+ * or undefined when the plan has no change for it either. The usual case is
144
+ * a planned create: `planAction: "create"`, `planActions`, and
145
+ * `planAfterUnknown` naming what stays unknown until apply (its `id` among
146
+ * them). A data block read during apply comes back as `planAction: "read"`.
147
+ *
148
+ * The reader puts these on the observation's `annotations`, not on a
149
+ * `resources` row, so the block stays absent and `lifecycle plan` still
150
+ * proposes the create.
151
+ */
152
+ export declare function plannedAttributesFor(address: string, index: PlanIndex): Record<string, unknown> | undefined;
153
+ //# sourceMappingURL=plan-observation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-observation.d.ts","sourceRoot":"","sources":["../src/plan-observation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,8EAA8E;AAC9E,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,YAC/B,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,wBAAwB,CAChB,CAAC;AAEX,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB,4DAA4D;IAC5D,KAAK,EAAE,OAAO,CAAC;IACf,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzC,+CAA+C;IAC/C,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACtC,uFAAuF;IACvF,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACjC;AAYD,uDAAuD;AACvD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAQ3E;AA4DD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE;IACN,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,EACD,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,eAAe,EAAE,CAgEnB;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAmEtD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAI7D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6B5F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAG3G"}