@f5-sales-demo/xcsh 23.2.1 → 23.3.3

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.
@@ -0,0 +1,428 @@
1
+ import { createHash, X509Certificate } from "node:crypto";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ import { blindfoldPrepare } from "@f5-sales-demo/pi-natives";
5
+ import type { HttpTransport, ResourceManifest } from "@f5-sales-demo/pi-resource-management";
6
+ import { API_CATALOG_DATA } from "../internal-urls/api-catalog-index.generated";
7
+ import type { BlindfoldArgs, BlindfoldOptions, BlindfoldReport, BlindfoldRuntime } from "./blindfold";
8
+ import contractBundle from "./blindfold-contract/blindfold-contract-v1.txt" with { type: "text" };
9
+ import contractLock from "./blindfold-contract/lock.json" with { type: "json" };
10
+ import {
11
+ canonicalBlindfoldDocument,
12
+ normalizeBlindfoldDocument,
13
+ parseBlindfoldDocument,
14
+ readBlindfoldResponse,
15
+ } from "./blindfold-documents";
16
+
17
+ const annotation = "f5-sales-demo.com/blindfold";
18
+ const label = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
19
+ const hash = (v: string | Uint8Array) => createHash("sha256").update(v).digest("hex");
20
+ const object = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === "object" && !Array.isArray(v);
21
+ function canonical(v: unknown): unknown {
22
+ return Array.isArray(v)
23
+ ? v.map(canonical)
24
+ : object(v)
25
+ ? Object.fromEntries(
26
+ Object.keys(v)
27
+ .sort()
28
+ .map(k => [k, canonical(v[k])]),
29
+ )
30
+ : v;
31
+ }
32
+ interface Entry {
33
+ chain: string;
34
+ spki: string;
35
+ context: string;
36
+ ciphertext: string;
37
+ algorithm: string;
38
+ }
39
+ interface Source {
40
+ id: string;
41
+ pointer: string;
42
+ certificate_file?: string;
43
+ private_key_file?: string;
44
+ pkcs12_file?: string;
45
+ passphrase_env?: string;
46
+ policy?: string;
47
+ }
48
+ function pointer(root: Record<string, unknown>, text: string): Record<string, unknown> {
49
+ if (!text.startsWith("/spec") || /~(?![01])/.test(text))
50
+ throw new Error("Blindfold pointer must target a recognized certificate node beneath /spec");
51
+ let value: unknown = root;
52
+ for (const token of text
53
+ .slice(1)
54
+ .split("/")
55
+ .map(v => v.replaceAll("~1", "/").replaceAll("~0", "~"))) {
56
+ if (Array.isArray(value)) {
57
+ if (!/^(0|[1-9][0-9]*)$/.test(token)) throw new Error("Invalid Blindfold array pointer");
58
+ value = value[Number(token)];
59
+ } else if (object(value) && Object.hasOwn(value, token)) value = value[token];
60
+ else throw new Error("Unresolved Blindfold certificate pointer");
61
+ }
62
+ if (!object(value)) throw new Error("Blindfold pointer must target a certificate object");
63
+ return value;
64
+ }
65
+ function identity(certificateUrl: string) {
66
+ if (!certificateUrl.startsWith("string:///")) throw new Error("Native certificate normalization failed");
67
+ const chain = Buffer.from(certificateUrl.slice(10), "base64");
68
+ const leaf = new X509Certificate(chain);
69
+ return { chain: hash(chain), spki: hash(leaf.publicKey.export({ format: "der", type: "spki" })) };
70
+ }
71
+ function provenance(remote: Record<string, unknown> | undefined): Record<string, Entry> {
72
+ const metadata = remote?.metadata;
73
+ const annotations = object(metadata) ? metadata.annotations : undefined;
74
+ const text = object(annotations) ? annotations[annotation] : undefined;
75
+ if (typeof text !== "string" || text.length > 131072) return {};
76
+ try {
77
+ const parsed = parseBlindfoldDocument(text);
78
+ if (!object(parsed) || parsed.version !== 1 || !object(parsed.entries)) return {};
79
+ for (const [id, entry] of Object.entries(parsed.entries)) {
80
+ if (
81
+ !label.test(id) ||
82
+ !object(entry) ||
83
+ !["RSA", "EC"].includes(String(entry.algorithm)) ||
84
+ [entry.chain, entry.spki, entry.context, entry.ciphertext].some(
85
+ v => typeof v !== "string" || !/^[0-9a-f]{64}$/.test(v),
86
+ )
87
+ )
88
+ return {};
89
+ }
90
+ return parsed.entries as Record<string, Entry>;
91
+ } catch {
92
+ return {};
93
+ }
94
+ }
95
+
96
+ export async function ensureBlindfold(
97
+ options: BlindfoldOptions,
98
+ args: BlindfoldArgs,
99
+ runtime: BlindfoldRuntime,
100
+ ): Promise<BlindfoldReport> {
101
+ const guard = () => {
102
+ if (runtime.signal?.aborted) throw new Error("Blindfold operation cancelled");
103
+ runtime.guard?.();
104
+ };
105
+ guard();
106
+ if (hash(contractBundle) !== contractLock.sha256 || contractLock.version !== 1)
107
+ throw new Error("Embedded Blindfold contract integrity failure");
108
+ if (runtime.planMode) throw new Error("Plan mode: Blindfold ensure is blocked");
109
+ const apiUrl = options.env.XCSH_API_URL?.replace(/\/+$/, ""),
110
+ token = options.env.XCSH_API_TOKEN;
111
+ if (!apiUrl || !token) throw new Error("Blindfold requires tenant API credentials");
112
+ const origin = new URL(apiUrl);
113
+ if (
114
+ origin.protocol !== "https:" ||
115
+ origin.username ||
116
+ origin.password ||
117
+ origin.search ||
118
+ origin.hash ||
119
+ origin.pathname !== "/"
120
+ )
121
+ throw new Error("Tenant API URL must be an HTTPS origin");
122
+ if (args.contextName && args.contextName !== options.env.XCSH_CONTEXT_NAME)
123
+ throw new Error("Blindfold context mismatch");
124
+ const fetcher = options.fetch ?? fetch;
125
+ const request = async (method: string, url: string, body?: Record<string, unknown>) => {
126
+ guard();
127
+ const signal = runtime.signal
128
+ ? AbortSignal.any([runtime.signal, AbortSignal.timeout(30000)])
129
+ : AbortSignal.timeout(30000);
130
+ try {
131
+ const r = await fetcher(url, {
132
+ method,
133
+ signal,
134
+ redirect: "error",
135
+ headers: {
136
+ Authorization: `APIToken ${token}`,
137
+ Accept: "application/json",
138
+ "Content-Type": "application/json",
139
+ },
140
+ body: body ? JSON.stringify(body) : undefined,
141
+ });
142
+ const parsed = r.ok ? await readBlindfoldResponse(r) : {};
143
+ guard();
144
+ return { status: r.status, body: object(parsed) ? parsed : {} };
145
+ } catch {
146
+ guard();
147
+ return { status: 0, body: {} };
148
+ }
149
+ };
150
+ const get = async (url: string) => {
151
+ const r = await request("GET", url);
152
+ if (r.status !== 200) throw new Error("Cannot retrieve Blindfold public material");
153
+ return r.body;
154
+ };
155
+ let raw: Record<string, unknown>,
156
+ sources: Source[],
157
+ base = options.cwd ?? process.cwd();
158
+ if (args.file) {
159
+ if (args.cert || args.key || args.bundle || args.name || args.passphraseEnv || args.policy)
160
+ throw new Error("Manifest ensure conflicts with named certificate inputs");
161
+ const filename = path.resolve(base, args.file);
162
+ const stat = await fs.stat(filename);
163
+ if (!stat.isFile() || stat.size > 2097152) throw new Error("Blindfold manifest exceeds input bounds");
164
+ const parsed = parseBlindfoldDocument(await fs.readFile(filename, "utf8"));
165
+ if (!object(parsed) || !object(parsed.metadata) || !object(parsed.spec))
166
+ throw new Error("Malformed Blindfold manifest");
167
+ raw = parsed;
168
+ const extension = raw["x-xcsh-blindfold"];
169
+ if (
170
+ !object(extension) ||
171
+ extension.version !== 1 ||
172
+ !Array.isArray(extension.certificates) ||
173
+ !extension.certificates.length
174
+ )
175
+ throw new Error("Require x-xcsh-blindfold version 1 certificates");
176
+ sources = extension.certificates as Source[];
177
+ delete raw["x-xcsh-blindfold"];
178
+ base = path.dirname(filename);
179
+ } else {
180
+ if (!args.name || !label.test(args.name) || (args.bundle ? args.cert || args.key : !args.cert || !args.key))
181
+ throw new Error("Ensure requires name and PEM files or a P12 bundle");
182
+ raw = {
183
+ kind: "certificate",
184
+ metadata: { name: args.name, namespace: args.namespace ?? options.env.XCSH_NAMESPACE },
185
+ spec: {},
186
+ };
187
+ sources = [
188
+ {
189
+ id: "default",
190
+ pointer: "/spec",
191
+ certificate_file: args.cert,
192
+ private_key_file: args.key,
193
+ pkcs12_file: args.bundle,
194
+ passphrase_env: args.passphraseEnv,
195
+ policy: args.policy,
196
+ },
197
+ ];
198
+ }
199
+ const metadata = raw.metadata as Record<string, unknown>,
200
+ kind = raw.kind;
201
+ const name = metadata.name,
202
+ namespace = args.namespace ?? metadata.namespace ?? options.env.XCSH_NAMESPACE;
203
+ if (
204
+ typeof kind !== "string" ||
205
+ typeof name !== "string" ||
206
+ !label.test(name) ||
207
+ typeof namespace !== "string" ||
208
+ !label.test(namespace)
209
+ )
210
+ throw new Error("Invalid manifest resource identity");
211
+ metadata.namespace = namespace;
212
+ const { kindResolver } = await import("../resource-management/index");
213
+ const { ResourceClient, validateManifest } = await import("@f5-sales-demo/pi-resource-management");
214
+ const resolved = kindResolver.resolveKind(kind);
215
+ const transport: HttpTransport = {
216
+ request: async req => {
217
+ const url = new URL(req.url);
218
+ if (req.method === "GET") url.searchParams.set("response_format", "2");
219
+ const r = await request(req.method, url.toString(), req.body);
220
+ const form = r.body.replace_form;
221
+ const body = object(form)
222
+ ? { ...form, ...(r.body.resource_version ? { resource_version: r.body.resource_version } : {}) }
223
+ : r.body;
224
+ return {
225
+ httpStatus: r.status,
226
+ body:
227
+ r.status >= 200 && r.status < 300
228
+ ? body
229
+ : { message: `Blindfold tenant request failed (HTTP ${r.status})` },
230
+ };
231
+ },
232
+ };
233
+ const client = new ResourceClient({ apiUrl, apiToken: token, namespace, transport });
234
+ const existing = await client.get(resolved, name, namespace);
235
+ guard();
236
+ if (existing.error && existing.error.kind !== "not_found")
237
+ throw new Error("Cannot inspect resource before Blindfold ensure");
238
+ const remote = existing.resource as Record<string, unknown> | undefined;
239
+ if (!args.file && remote)
240
+ raw = {
241
+ kind,
242
+ ...remote,
243
+ metadata: { ...(remote.metadata as Record<string, unknown>), name, namespace },
244
+ spec: { ...(remote.spec as Record<string, unknown>) },
245
+ };
246
+ const publicDocument = canonicalBlindfoldDocument(
247
+ await get(`${apiUrl}/api/secret_management/get_public_key`),
248
+ "public-key",
249
+ );
250
+ const pub = normalizeBlindfoldDocument(publicDocument, "public-key");
251
+ const entries = provenance(remote),
252
+ ids = new Set<string>(),
253
+ pointers = new Set<string>();
254
+ let changed = !remote;
255
+ const policies = new Map<string, unknown>();
256
+ for (const source of sources) {
257
+ guard();
258
+ if (
259
+ !object(source) ||
260
+ !label.test(source.id) ||
261
+ typeof source.pointer !== "string" ||
262
+ ids.has(source.id) ||
263
+ pointers.has(source.pointer)
264
+ )
265
+ throw new Error("Blindfold IDs and pointers must be unique");
266
+ ids.add(source.id);
267
+ pointers.add(source.pointer);
268
+ const node = pointer(raw, source.pointer);
269
+ // Resolve a concrete JSON pointer against embedded operation field metadata.
270
+ const fieldPath = source.pointer
271
+ .slice(1)
272
+ .replace(/\/(0|[1-9][0-9]*)(?=\/|$)/g, "[]")
273
+ .replaceAll("/", ".");
274
+ const recognized = Object.values(API_CATALOG_DATA).some(category =>
275
+ category.operations.some(
276
+ operation =>
277
+ operation.method.toLowerCase() === "post" &&
278
+ operation.path
279
+ .replaceAll("{metadata.namespace}", "{namespace}")
280
+ .replaceAll("{metadata.name}", "{name}") === resolved.paths.create &&
281
+ Object.hasOwn(operation.fieldMetadata ?? {}, `${fieldPath}.certificate_url`) &&
282
+ Object.keys(operation.fieldMetadata ?? {}).some(field => field.startsWith(`${fieldPath}.private_key.`)),
283
+ ),
284
+ );
285
+ if (!recognized && !(source.pointer === "/spec" && resolved.kind === "certificate"))
286
+ throw new Error("Unsupported Blindfold certificate node");
287
+ if (
288
+ source.pkcs12_file
289
+ ? source.certificate_file || source.private_key_file
290
+ : !source.certificate_file || !source.private_key_file
291
+ )
292
+ throw new Error("Require PEM pair or one P12 file");
293
+ const policyName = source.policy ?? "shared/ves-io-allow-volterra";
294
+ const parts = policyName.split("/");
295
+ if (parts.length !== 2 || parts.some(v => !label.test(v))) throw new Error("Invalid Blindfold policy");
296
+ let policyDocument = policies.get(policyName);
297
+ if (!policyDocument) {
298
+ policyDocument = canonicalBlindfoldDocument(
299
+ await get(
300
+ `${apiUrl}/api/secret_management/namespaces/${parts[0]}/secret_policys/${parts[1]}/get_policy_document`,
301
+ ),
302
+ "policy",
303
+ );
304
+ policies.set(policyName, policyDocument);
305
+ }
306
+ const policy = normalizeBlindfoldDocument(policyDocument, "policy");
307
+ if (policy.data.tenant !== pub.data.tenant) throw new Error("Blindfold material tenant mismatch");
308
+ const context = hash(JSON.stringify(canonical([publicDocument, policyDocument])));
309
+ const input = {
310
+ publicKeyJson: JSON.stringify(pub),
311
+ policyJson: JSON.stringify(policy),
312
+ cert: source.certificate_file ? path.resolve(base, source.certificate_file) : undefined,
313
+ key: source.private_key_file ? path.resolve(base, source.private_key_file) : undefined,
314
+ bundle: source.pkcs12_file ? path.resolve(base, source.pkcs12_file) : undefined,
315
+ passphraseEnv: source.passphrase_env,
316
+ };
317
+ const inspect = (options.prepare ?? blindfoldPrepare)({ ...input, inspectOnly: true });
318
+ guard();
319
+ if (!inspect.certificateUrl || !inspect.algorithm) throw new Error("Native certificate inspection failed");
320
+ const publicIdentity = identity(inspect.certificateUrl),
321
+ old = entries[source.id];
322
+ let oldNode: Record<string, unknown> = {};
323
+ try {
324
+ if (remote) oldNode = nodeByEntry(remote, old) ?? pointer(remote, source.pointer);
325
+ } catch {}
326
+ const privateKey = oldNode.private_key,
327
+ blind = object(privateKey) ? privateKey.blindfold_secret_info : undefined,
328
+ location = object(blind) ? blind.location : undefined;
329
+ if (old?.algorithm && old.algorithm !== inspect.algorithm)
330
+ throw new Error("RSA/EC algorithm changes require a new certificate and reference cutover");
331
+ const equivalent =
332
+ typeof location === "string" &&
333
+ oldNode.certificate_url === inspect.certificateUrl &&
334
+ old?.chain === publicIdentity.chain &&
335
+ old.spki === publicIdentity.spki &&
336
+ old.context === context &&
337
+ old.ciphertext === hash(location) &&
338
+ old.algorithm === inspect.algorithm;
339
+ let encrypted = equivalent ? location : "";
340
+ if (!equivalent) {
341
+ changed = true;
342
+ if (!args.dryRun) encrypted = (options.prepare ?? blindfoldPrepare)(input).location;
343
+ }
344
+ node.certificate_url = inspect.certificateUrl;
345
+ node.private_key = { blindfold_secret_info: { location: encrypted } };
346
+ entries[source.id] = { ...publicIdentity, context, ciphertext: hash(encrypted), algorithm: inspect.algorithm };
347
+ }
348
+ const oldMeta = remote?.metadata;
349
+ const oldAnnotations = object(oldMeta) && object(oldMeta.annotations) ? oldMeta.annotations : {};
350
+ const desiredMeta = raw.metadata as Record<string, unknown>;
351
+ desiredMeta.annotations = {
352
+ ...oldAnnotations,
353
+ ...(object(desiredMeta.annotations) ? desiredMeta.annotations : {}),
354
+ [annotation]: JSON.stringify({ version: 1, entries }),
355
+ };
356
+ if (remote?.resource_version) raw.resource_version = remote.resource_version;
357
+ const report: BlindfoldReport = {
358
+ operation: "ensure",
359
+ status: args.dryRun ? "dry-run" : "unchanged",
360
+ target: { apiUrl, tenant: String(pub.data.tenant), namespace, name },
361
+ artifacts: [],
362
+ };
363
+ if (!changed && !args.file) return report;
364
+ const manifest: ResourceManifest = {
365
+ kind,
366
+ metadata: { name, namespace },
367
+ spec: raw.spec as Record<string, unknown>,
368
+ rawObject: raw,
369
+ };
370
+ if (
371
+ !args.dryRun &&
372
+ !validateManifest(manifest, kindResolver, namespace, { operation: remote ? "update" : "create" }).result.valid
373
+ )
374
+ throw new Error("Blindfold manifest failed validation");
375
+ if (args.dryRun) return report;
376
+ guard();
377
+ const result = remote
378
+ ? await client.update(manifest, resolved, namespace)
379
+ : await client.create(manifest, resolved, namespace);
380
+ if (result.status === "error" && result.error?.httpStatus === 409)
381
+ throw new Error("Blindfold concurrency conflict; refresh and review before retrying");
382
+ const verify = await client.get(resolved, name, namespace);
383
+ guard();
384
+ if (!verify.resource) throw new Error("Blindfold write outcome unresolved; inspect resource before retrying");
385
+ for (const source of sources) {
386
+ const expected = pointer(raw, source.pointer),
387
+ actual = pointer(verify.resource, source.pointer);
388
+ if (
389
+ expected.certificate_url !== actual.certificate_url ||
390
+ remoteEncryptedLocation(expected) !== remoteEncryptedLocation(actual)
391
+ )
392
+ throw new Error("Blindfold write outcome unresolved; inspect resource before retrying");
393
+ }
394
+ if (JSON.stringify(canonical(provenance(verify.resource))) !== JSON.stringify(canonical(entries)))
395
+ throw new Error("Blindfold provenance readback failed");
396
+ report.status = result.status === "unchanged" ? "unchanged" : "accepted";
397
+ report.readiness = "unverified";
398
+ report.reconciled = result.status === "error";
399
+ return report;
400
+ }
401
+
402
+ function remoteEncryptedLocation(node: Record<string, unknown>): unknown {
403
+ const key = node.private_key;
404
+ const encrypted = object(key) ? key.blindfold_secret_info : undefined;
405
+ return object(encrypted) ? encrypted.location : undefined;
406
+ }
407
+
408
+ function nodeByEntry(remote: Record<string, unknown>, entry: Entry | undefined): Record<string, unknown> | undefined {
409
+ if (!entry) return undefined;
410
+ let found: Record<string, unknown> | undefined;
411
+ const visit = (value: unknown) => {
412
+ if (Array.isArray(value)) {
413
+ for (const child of value) visit(child);
414
+ } else if (object(value)) {
415
+ const location = remoteEncryptedLocation(value),
416
+ certificate = value.certificate_url;
417
+ if (typeof location === "string" && hash(location) === entry.ciphertext && typeof certificate === "string") {
418
+ try {
419
+ const normalized = identity(certificate);
420
+ if (normalized.chain === entry.chain && normalized.spki === entry.spki) found = value;
421
+ } catch {}
422
+ }
423
+ for (const child of Object.values(value)) visit(child);
424
+ }
425
+ };
426
+ visit(remote.spec);
427
+ return found;
428
+ }
@@ -7,7 +7,6 @@ import {
7
7
  blindfoldPrepare,
8
8
  } from "@f5-sales-demo/pi-natives";
9
9
  import type { HttpTransport, ResourceManifest } from "@f5-sales-demo/pi-resource-management";
10
-
11
10
  import {
12
11
  canonicalBlindfoldDocument,
13
12
  normalizeBlindfoldDocument,
@@ -15,11 +14,21 @@ import {
15
14
  readBlindfoldResponse,
16
15
  serializeBlindfoldDocument,
17
16
  } from "./blindfold-documents";
17
+ import { ensureBlindfold } from "./blindfold-ensure";
18
18
 
19
- export const BLINDFOLD_OPERATIONS = ["public-key", "policy", "encrypt", "certificate", "create", "replace"] as const;
19
+ export const BLINDFOLD_OPERATIONS = [
20
+ "public-key",
21
+ "policy",
22
+ "encrypt",
23
+ "certificate",
24
+ "create",
25
+ "replace",
26
+ "ensure",
27
+ ] as const;
20
28
  export type BlindfoldOperation = (typeof BLINDFOLD_OPERATIONS)[number];
21
29
  export interface BlindfoldArgs {
22
30
  operation: BlindfoldOperation;
31
+ file?: string;
23
32
  compatibility?: boolean;
24
33
  encoding?: "base64" | "location";
25
34
  outfile?: string;
@@ -41,7 +50,7 @@ export interface BlindfoldArgs {
41
50
  dryRun?: "client";
42
51
  }
43
52
  export interface BlindfoldReport {
44
- status: "material-retrieved" | "prepared" | "dry-run" | "accepted";
53
+ status: "material-retrieved" | "prepared" | "dry-run" | "accepted" | "unchanged";
45
54
  operation: BlindfoldOperation;
46
55
  materialSource?: "retrieved" | "supplied";
47
56
  target: { apiUrl?: string; tenant: string; namespace?: string; name?: string; contextName?: string };
@@ -57,7 +66,7 @@ export interface BlindfoldRuntime {
57
66
  planMode?: boolean;
58
67
  guard?: () => void;
59
68
  }
60
- interface Options {
69
+ export interface BlindfoldOptions {
61
70
  emit?: (content: string) => void;
62
71
  env: Readonly<Record<string, string | undefined>>;
63
72
  cwd?: string;
@@ -109,11 +118,12 @@ export async function writeBlindfoldArtifact(
109
118
  }
110
119
  /** Holds encrypted material locally; callers receive only the public report. */
111
120
  export class BlindfoldService {
112
- readonly #options: Options;
113
- constructor(options: Options) {
121
+ readonly #options: BlindfoldOptions;
122
+ constructor(options: BlindfoldOptions) {
114
123
  this.#options = options;
115
124
  }
116
125
  async run(args: BlindfoldArgs, runtime: BlindfoldRuntime = {}): Promise<BlindfoldReport> {
126
+ if (args.operation === "ensure") return ensureBlindfold(this.#options, args, runtime);
117
127
  const guard = () => {
118
128
  if (runtime.signal?.aborted) throw new Error("Blindfold operation cancelled");
119
129
  runtime.guard?.();
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { TerraformDocumentationRepository } from "./internal-urls/terraform-documentation";
4
4
  import { EMBEDDED_TERRAFORM_DOCUMENTATION } from "./internal-urls/terraform-documentation-assets.generated";
5
5
  import type { InternalUrl } from "./internal-urls/types";
6
+ import { assertTerraformPortSection } from "./terraform-port-smoke";
6
7
 
7
8
  export async function runTerraformDocumentationSmoke(): Promise<string> {
8
9
  if (!EMBEDDED_TERRAFORM_DOCUMENTATION) throw new Error("Terraform smoke requires embedded assets");
@@ -41,8 +42,7 @@ export async function runTerraformDocumentationSmoke(): Promise<string> {
41
42
  const section = await read(
42
43
  "xcsh://terraform-documentation/documentation/resources/http_loadbalancer/properties/https/index.md#schema-https--port",
43
44
  );
44
- if (!section.includes("### port") || !section.includes("Validators"))
45
- throw new Error("Terraform explicit-anchor smoke failed");
45
+ assertTerraformPortSection(section);
46
46
  trace.push({ event: "terraform-explicit-anchor", outcome: "complete-property" });
47
47
  const database = await repository.database();
48
48
  const leaf =
@@ -0,0 +1,15 @@
1
+ /** Require the exact published port property and receipt-pinned constraint. */
2
+ export function assertTerraformPortSection(section: string): void {
3
+ if (!section.includes('<a id="schema-https--port"></a>') || !/^### port(?: property)?\s*$/m.test(section))
4
+ throw new Error("Terraform explicit-anchor smoke failed");
5
+ const fences = [...section.matchAll(/^```json\s*\n([\s\S]*?)^```\s*$/gm)];
6
+ const constrained = fences.some(match => {
7
+ try {
8
+ const rules = JSON.parse(match[1]!) as Record<string, Record<string, unknown>>;
9
+ return rules["x-validation-rules"]?.["ves.io.schema.rules.uint32.lte"] === "65535";
10
+ } catch {
11
+ return false;
12
+ }
13
+ });
14
+ if (!constrained) throw new Error("Terraform port constraint smoke failed");
15
+ }
@@ -11,6 +11,7 @@ import type { ToolSession } from "./index";
11
11
  export const blindfoldSchema = Type.Object(
12
12
  {
13
13
  operation: Type.Union(BLINDFOLD_OPERATIONS.map(v => Type.Literal(v))),
14
+ file: Type.Optional(Type.String()),
14
15
  input: Type.Optional(Type.String()),
15
16
  publicKey: Type.Optional(Type.String()),
16
17
  policyDocument: Type.Optional(Type.String()),
@@ -90,7 +91,11 @@ export class XcshBlindfoldTool implements AgentTool<typeof blindfoldSchema> {
90
91
  throw new Error("Blindfold context changed; retry against the selected context");
91
92
  if (
92
93
  this.session.getPlanModeState?.()?.enabled &&
93
- (args.outputFile || args.resultFile || args.operation === "create" || args.operation === "replace")
94
+ (args.outputFile ||
95
+ args.resultFile ||
96
+ args.operation === "create" ||
97
+ args.operation === "replace" ||
98
+ args.operation === "ensure")
94
99
  )
95
100
  throw new Error("Plan mode: Blindfold deployments and artifact writes are blocked");
96
101
  };