@intentius/chant-lexicon-gcp 0.124.37 → 0.124.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@
37
37
  * quietly implying it is not there. Widening the table is #1209's stated
38
38
  * non-goal and belongs with the applier.
39
39
  */
40
- import { type ResourceMapper } from "../op/activities/gcp-apply.js";
40
+ import { type GcpResource, type ResourceMapper } from "../op/activities/gcp-apply.js";
41
41
  /** Where to read, and as whom. */
42
42
  export interface GcpReadClientOptions {
43
43
  /** Endpoint override for every kind (floci-gcp `http://localhost:4588`). Omit for real GCP. */
@@ -46,6 +46,12 @@ export interface GcpReadClientOptions {
46
46
  project: string;
47
47
  /** Injectable HTTP, so tests never touch the network. */
48
48
  http?: GcpReadHttp;
49
+ /**
50
+ * The declared resources, refs resolved, as apply passes them (#4251): an
51
+ * IAMPolicyMember on a RunService named by `name` takes the service's region
52
+ * from its declaration, which a read otherwise can't know.
53
+ */
54
+ manifest?: readonly GcpResource[];
49
55
  }
50
56
  /** Injectable HTTP client — mirrors the applier's `GcpHttp`. A body is sent only for a POST read (`:getIamPolicy`). */
51
57
  export type GcpReadHttp = (method: string, url: string, body?: unknown) => Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"read-client.d.ts","sourceRoot":"","sources":["../../src/api/read-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5F,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,uHAAuH;AACvH,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAUrH;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,KAAK;IAGnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAF1B,YACE,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,MAAM,YAAA,EAIzB;CACF;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhD;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEtE;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiDlC"}
1
+ {"version":3,"file":"read-client.d.ts","sourceRoot":"","sources":["../../src/api/read-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAW,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5F,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACnC;AAED,uHAAuH;AACvH,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAUrH;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,KAAK;IAGnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IAF1B,YACE,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,MAAM,YAAA,EAIzB;CACF;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhD;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEtE;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiDlC"}
@@ -82,7 +82,7 @@ export async function getResource(client, kind, name, declared) {
82
82
  metadata: { name },
83
83
  spec: declared?.spec ?? declared ?? {},
84
84
  };
85
- const ctx = { base, project: client.project };
85
+ const ctx = { base, project: client.project, ...(client.manifest ? { manifest: client.manifest } : {}) };
86
86
  if (mapper.accepts && !mapper.accepts(resource, ctx)) {
87
87
  throw new GcpReadError(`no REST read for ${kind}/${name}: its target has no endpoint chant can read`);
88
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"deep-observe.d.ts","sourceRoot":"","sources":["../src/deep-observe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAajE,OAAO,EAAE,yBAAyB,EAAE,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWtF;AAUD;6EAC6E;AAC7E,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA0OD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASnH;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAuF5G"}
1
+ {"version":3,"file":"deep-observe.d.ts","sourceRoot":"","sources":["../src/deep-observe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAajE,OAAO,EAAE,yBAAyB,EAAE,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWtF;AAUD;6EAC6E;AAC7E,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA0OD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASnH;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyF5G"}
@@ -55,7 +55,7 @@
55
55
  */
56
56
  import { deepObservation, normalizeDeepProperties } from "@intentius/chant/deep-observation";
57
57
  import { hasOwnershipMarker } from "@intentius/chant/ownership";
58
- import { deriveGVK, manifestProjectAnnotations, readTargets, resolveReadProject } from "./describe-resources.js";
58
+ import { deriveGVK, manifestProjectAnnotations, readManifest, readTargets, resolveReadProject } from "./describe-resources.js";
59
59
  import { getResource, mapperForKind, GcpReadError, isNotFound } from "./api/read-client.js";
60
60
  import { gcpDeepNormalizationHooks } from "./deep-observe-hooks.js";
61
61
  /** The labels the applier stamps — see describe-resources.ts. */
@@ -365,6 +365,7 @@ export async function observeResourcesDeepGcp(options) {
365
365
  const endpoint = process.env.GCP_ENDPOINT_URL;
366
366
  const manifestProjects = manifestProjectAnnotations(options.buildOutput);
367
367
  const targets = readTargets(options.entities);
368
+ const manifest = readManifest(options.entities, targets);
368
369
  const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
369
370
  const gvk = deriveGVK(entityType);
370
371
  if (!gvk || !mapperForKind(gvk.kind)) {
@@ -384,6 +385,7 @@ export async function observeResourcesDeepGcp(options) {
384
385
  client = {
385
386
  project: resolveReadProject(gvk.kind, name, metadata, manifestProjects, props),
386
387
  ...(endpoint ? { endpoint } : {}),
388
+ manifest,
387
389
  };
388
390
  }
389
391
  catch (err) {
@@ -36,6 +36,7 @@
36
36
  * dropped.
37
37
  */
38
38
  import type { ObservationResult } from "@intentius/chant/lexicon";
39
+ import { type GcpResource } from "./op/activities/gcp-apply.js";
39
40
  /** The parts of a GCP REST payload this reader looks at. Every field is
40
41
  * optional because they vary by kind — a bucket has `id` and `labels`, a
41
42
  * Pub/Sub topic has neither. */
@@ -105,6 +106,18 @@ export declare function readTargets(entities: Map<string, {
105
106
  name: string;
106
107
  props: Record<string, unknown>;
107
108
  }>;
109
+ /**
110
+ * The declared resources as the applier's manifest holds them (kind, CNRM
111
+ * name, spec), for a read that needs another declared resource: an
112
+ * IAMPolicyMember on a RunService takes the service's region from it (#4251).
113
+ */
114
+ export declare function readManifest(entities: Map<string, {
115
+ entityType: string;
116
+ props: Record<string, unknown>;
117
+ }>, targets: Map<string, {
118
+ name: string;
119
+ props: Record<string, unknown>;
120
+ }>): GcpResource[];
108
121
  /**
109
122
  * Status from a REST payload.
110
123
  *
@@ -1 +1 @@
1
- {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAsC,MAAM,0BAA0B,CAAC;AAsBtG;;gCAEgC;AAChC,UAAU,eAAe;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACxF,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAIpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgB/F;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,SAAS,EAC9D,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAUR;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC5E,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAY/D;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAY3D;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiH7B"}
1
+ {"version":3,"file":"describe-resources.d.ts","sourceRoot":"","sources":["../src/describe-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAsC,MAAM,0BAA0B,CAAC;AAItG,OAAO,EAAgD,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAkB3G;;gCAEgC;AAChC,UAAU,eAAe;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACxF,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAIpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgB/F;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,SAAS,EAC9D,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAUR;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC5E,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAY/D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EAC7E,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GACrE,WAAW,EAAE,CAUf;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAY3D;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmH7B"}
@@ -137,6 +137,23 @@ export function readTargets(entities) {
137
137
  }
138
138
  return out;
139
139
  }
140
+ /**
141
+ * The declared resources as the applier's manifest holds them (kind, CNRM
142
+ * name, spec), for a read that needs another declared resource: an
143
+ * IAMPolicyMember on a RunService takes the service's region from it (#4251).
144
+ */
145
+ export function readManifest(entities, targets) {
146
+ const out = [];
147
+ for (const [logical, { entityType }] of entities) {
148
+ const gvk = deriveGVK(entityType);
149
+ const target = targets.get(logical);
150
+ if (!gvk || !target)
151
+ continue;
152
+ const spec = target.props.spec ?? target.props;
153
+ out.push({ kind: gvk.kind, metadata: { name: target.name }, spec });
154
+ }
155
+ return out;
156
+ }
140
157
  function pruneUndefined(obj) {
141
158
  const out = {};
142
159
  for (const [k, v] of Object.entries(obj)) {
@@ -185,6 +202,7 @@ export async function describeResources(options) {
185
202
  let warnedOwnership = false;
186
203
  const manifestProjects = manifestProjectAnnotations(options.buildOutput);
187
204
  const targets = readTargets(options.entities);
205
+ const manifest = readManifest(options.entities, targets);
188
206
  const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
189
207
  const gvk = deriveGVK(entityType);
190
208
  if (!gvk) {
@@ -212,6 +230,7 @@ export async function describeResources(options) {
212
230
  client = {
213
231
  project: resolveReadProject(gvk.kind, name, metadata, manifestProjects, props),
214
232
  ...(endpoint ? { endpoint } : {}),
233
+ manifest,
215
234
  };
216
235
  }
217
236
  catch (err) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "648e7fb96c2a44b72459dc70e777c22836de776eb5f5b8017f8c6057135062a3",
4
+ "manifest.json": "abad2dc852fe087b6167993403f8fe827512b85c33a02d66b35dab64b4b0b5f3",
5
5
  "meta.json": "75643660010c044c6f465bef2688a864ab30cc96fd9b68111969749748ac64ca",
6
6
  "types/index.d.ts": "da1ebc6bcf83e1a018a773b59714194aeeb74ae1d5c1252b737b7e730ca98bd5",
7
7
  "rules/hardcoded-project.ts": "6d5ae9efa74d73800eebe1a0a0d43c619ee0fdebe3347195df93fcbe63b34f0d",
@@ -40,5 +40,5 @@
40
40
  "skills/chant-gcp-patterns.md": "4c6809fc7c37fa1879379ad4c0e2d114dec9497bc6eb9d1bca4e79fddea4f509",
41
41
  "skills/chant-gcp-gke.md": "be277019da9a722c851e47cd2dfb9c9536668948c3535fb20db7697e934c4e2b"
42
42
  },
43
- "composite": "12781f2fbe08546cbfed0496ef97a0cfc6f301c5576bfafad2cbd4bf3cac39f3"
43
+ "composite": "2417c2778b2c78b3a629c866e8b25766423b79094722bdec3ef566b47a151c31"
44
44
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gcp",
3
- "version": "0.124.37",
3
+ "version": "0.124.38",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "GCP",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-gcp",
3
- "version": "0.124.37",
3
+ "version": "0.124.38",
4
4
  "description": "Google Cloud lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -76,6 +76,6 @@
76
76
  "typescript": "^5.9.3"
77
77
  },
78
78
  "peerDependencies": {
79
- "@intentius/chant": "^0.124.37"
79
+ "@intentius/chant": "^0.124.38"
80
80
  }
81
81
  }
@@ -47,6 +47,12 @@ export interface GcpReadClientOptions {
47
47
  project: string;
48
48
  /** Injectable HTTP, so tests never touch the network. */
49
49
  http?: GcpReadHttp;
50
+ /**
51
+ * The declared resources, refs resolved, as apply passes them (#4251): an
52
+ * IAMPolicyMember on a RunService named by `name` takes the service's region
53
+ * from its declaration, which a read otherwise can't know.
54
+ */
55
+ manifest?: readonly GcpResource[];
50
56
  }
51
57
 
52
58
  /** Injectable HTTP client — mirrors the applier's `GcpHttp`. A body is sent only for a POST read (`:getIamPolicy`). */
@@ -108,7 +114,7 @@ export async function getResource(
108
114
  spec: (declared?.spec as Record<string, unknown> | undefined) ?? declared ?? {},
109
115
  };
110
116
 
111
- const ctx = { base, project: client.project };
117
+ const ctx = { base, project: client.project, ...(client.manifest ? { manifest: client.manifest } : {}) };
112
118
  if (mapper.accepts && !mapper.accepts(resource, ctx)) {
113
119
  throw new GcpReadError(`no REST read for ${kind}/${name}: its target has no endpoint chant can read`);
114
120
  }
@@ -61,7 +61,7 @@ import type {
61
61
  } from "@intentius/chant/lexicon";
62
62
  import { deepObservation, normalizeDeepProperties } from "@intentius/chant/deep-observation";
63
63
  import { hasOwnershipMarker, type ChannelKeys } from "@intentius/chant/ownership";
64
- import { deriveGVK, manifestProjectAnnotations, readTargets, resolveReadProject } from "./describe-resources";
64
+ import { deriveGVK, manifestProjectAnnotations, readManifest, readTargets, resolveReadProject } from "./describe-resources";
65
65
  import { getResource, mapperForKind, GcpReadError, isNotFound, type GcpReadClientOptions } from "./api/read-client";
66
66
  import { gcpDeepNormalizationHooks } from "./deep-observe-hooks";
67
67
 
@@ -388,6 +388,7 @@ export async function observeResourcesDeepGcp(options: GcpDeepObserveOptions): P
388
388
  const endpoint = process.env.GCP_ENDPOINT_URL;
389
389
  const manifestProjects = manifestProjectAnnotations(options.buildOutput);
390
390
  const targets = readTargets(options.entities);
391
+ const manifest = readManifest(options.entities, targets);
391
392
 
392
393
  const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
393
394
  const gvk = deriveGVK(entityType);
@@ -410,6 +411,7 @@ export async function observeResourcesDeepGcp(options: GcpDeepObserveOptions): P
410
411
  client = {
411
412
  project: resolveReadProject(gvk.kind, name, metadata, manifestProjects, props),
412
413
  ...(endpoint ? { endpoint } : {}),
414
+ manifest,
413
415
  };
414
416
  } catch (err) {
415
417
  unobserved[entityName] = {
@@ -40,7 +40,7 @@ import type { ObservationResult, ResourceMetadata, UnobservedEntity } from "@int
40
40
  import { observation } from "@intentius/chant/observation";
41
41
  import { hasOwnershipMarker, classifyOwnership, readOwnership, type ChannelKeys } from "@intentius/chant/ownership";
42
42
  import { getResource, mapperForKind, GcpReadError, isNotFound, type GcpReadClientOptions } from "./api/read-client";
43
- import { NO_PROJECT, parseManifest, projectRefProject } from "./op/activities/gcp-apply";
43
+ import { NO_PROJECT, parseManifest, projectRefProject, type GcpResource } from "./op/activities/gcp-apply";
44
44
  import { cnrmNameOf, resolveCnrmRefs, resolveGVK } from "./serializer";
45
45
 
46
46
  /**
@@ -167,6 +167,26 @@ export function readTargets(
167
167
  return out;
168
168
  }
169
169
 
170
+ /**
171
+ * The declared resources as the applier's manifest holds them (kind, CNRM
172
+ * name, spec), for a read that needs another declared resource: an
173
+ * IAMPolicyMember on a RunService takes the service's region from it (#4251).
174
+ */
175
+ export function readManifest(
176
+ entities: Map<string, { entityType: string; props: Record<string, unknown> }>,
177
+ targets: Map<string, { name: string; props: Record<string, unknown> }>,
178
+ ): GcpResource[] {
179
+ const out: GcpResource[] = [];
180
+ for (const [logical, { entityType }] of entities) {
181
+ const gvk = deriveGVK(entityType);
182
+ const target = targets.get(logical);
183
+ if (!gvk || !target) continue;
184
+ const spec = (target.props.spec as Record<string, unknown> | undefined) ?? target.props;
185
+ out.push({ kind: gvk.kind, metadata: { name: target.name }, spec });
186
+ }
187
+ return out;
188
+ }
189
+
170
190
  function pruneUndefined<T extends Record<string, unknown>>(obj: T): Record<string, unknown> {
171
191
  const out: Record<string, unknown> = {};
172
192
  for (const [k, v] of Object.entries(obj)) {
@@ -223,6 +243,7 @@ export async function describeResources(options: {
223
243
 
224
244
  const manifestProjects = manifestProjectAnnotations(options.buildOutput);
225
245
  const targets = readTargets(options.entities);
246
+ const manifest = readManifest(options.entities, targets);
226
247
 
227
248
  const reads = [...options.entities].map(async ([entityName, { entityType }]) => {
228
249
  const gvk = deriveGVK(entityType);
@@ -253,6 +274,7 @@ export async function describeResources(options: {
253
274
  client = {
254
275
  project: resolveReadProject(gvk.kind, name, metadata, manifestProjects, props),
255
276
  ...(endpoint ? { endpoint } : {}),
277
+ manifest,
256
278
  };
257
279
  } catch (err) {
258
280
  // No project resolvable — nothing was queried, so nothing is known.
@@ -1,4 +1,4 @@
1
- import { describe, test, expect, beforeEach, afterEach } from "vitest";
1
+ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
2
2
  import { mkdir, writeFile, rm, realpath } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { tmpdir } from "node:os";
@@ -6,9 +6,9 @@ import type { Declarable } from "@intentius/chant/declarable";
6
6
  import { loadChantConfig } from "@intentius/chant/config";
7
7
  import { armSandboxConfigEvaluation, resetSandboxConfigEvaluationForTests } from "@intentius/chant/config-sandbox";
8
8
  import { gcpSerializer } from "./serializer";
9
- import { manifestProjectAnnotations, resolveReadProject } from "./describe-resources";
9
+ import { describeResources, manifestProjectAnnotations, resolveReadProject } from "./describe-resources";
10
10
  import { parseManifest, resolveGcpProject } from "./op/activities/gcp-apply";
11
- import { CloudRunService, StorageBucket } from "./generated";
11
+ import { CloudRunService, PubSubTopic, StorageBucket } from "./generated";
12
12
  import { defaultAnnotations } from "./default-labels";
13
13
  import { GCP } from "./pseudo";
14
14
 
@@ -117,3 +117,50 @@ describe("gcp.project under --sandbox (#4252)", () => {
117
117
  }
118
118
  });
119
119
  });
120
+
121
+ describe("the live read takes gcp.project with no project env var (#4252)", () => {
122
+ let saved: NodeJS.ProcessEnv;
123
+ const fetchMock = vi.fn();
124
+ beforeEach(() => {
125
+ saved = { ...process.env };
126
+ delete process.env.GCP_PROJECT_ID;
127
+ delete process.env.GOOGLE_CLOUD_PROJECT;
128
+ delete process.env.GCP_ENDPOINT_URL;
129
+ fetchMock.mockReset();
130
+ fetchMock.mockImplementation(async (url: string) => new Response(JSON.stringify({ name: "projects/p/topics/assets" }), { status: 200 }));
131
+ vi.stubGlobal("fetch", fetchMock);
132
+ });
133
+ afterEach(() => {
134
+ process.env = saved;
135
+ vi.unstubAllGlobals();
136
+ });
137
+
138
+ async function read(config: Record<string, unknown> | undefined, props: Record<string, unknown>) {
139
+ const yaml = build({ assets: new PubSubTopic(props as never) }, config);
140
+ return describeResources({
141
+ environment: "local",
142
+ buildOutput: yaml,
143
+ entityNames: ["assets"],
144
+ entities: new Map([["assets", { entityType: "GCP::Pubsub::Topic", props }]]),
145
+ });
146
+ }
147
+
148
+ test("only gcp.project in config: the read targets that project", async () => {
149
+ const result = await read({ gcp: { project: "three-cloud" } }, { metadata: { name: "assets" } });
150
+ expect(result.unobserved?.assets).toBeUndefined();
151
+ expect(result.resources.assets).toBeDefined();
152
+ expect(String(fetchMock.mock.calls[0]?.[0])).toContain("three-cloud");
153
+ });
154
+
155
+ test("only a project-id annotation on the resource: the read targets that project", async () => {
156
+ const result = await read(undefined, { metadata: { name: "assets", annotations: { [ANNOTATION]: "annotated" } } });
157
+ expect(result.unobserved?.assets).toBeUndefined();
158
+ expect(String(fetchMock.mock.calls[0]?.[0])).toContain("annotated");
159
+ });
160
+
161
+ test("neither: no-binding, naming every place a project can come from", async () => {
162
+ const result = await read(undefined, { metadata: { name: "assets" } });
163
+ expect(result.unobserved?.assets?.reason).toBe("no-binding");
164
+ expect(result.unobserved?.assets?.detail).toMatch(/gcp\.project in chant\.config/);
165
+ });
166
+ });
@@ -1,5 +1,6 @@
1
1
  import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import { describeResources } from "./describe-resources";
3
+ import { CloudRunServiceComposite } from "./composites/cloud-run-service";
3
4
  import {
4
5
  BackendService,
5
6
  CloudRunService,
@@ -133,3 +134,62 @@ describe("gcp live read resolves .name references as the build does (#4251)", ()
133
134
  expect(out.unobserved?.publicIam).toBeUndefined();
134
135
  });
135
136
  });
137
+
138
+ /**
139
+ * #4251, the IAM binding behold saw as pending: the public IAMPolicyMember of a
140
+ * CloudRunServiceComposite whose service runs outside us-central1. The binding
141
+ * names its service by `name`, so the read has to take the service's region from
142
+ * the declared service, as apply does, or it asks a region where the service
143
+ * isn't, gets a 404 and reports the binding absent.
144
+ */
145
+ describe("the public binding of a service outside us-central1 reads as observed (#4251)", () => {
146
+ const policyUrl = "/v2/projects/three-cloud/locations/asia-northeast1/services/three-cloud-site:getIamPolicy";
147
+
148
+ function composite() {
149
+ const { service, publicIam } = CloudRunServiceComposite({
150
+ name: "three-cloud-site",
151
+ image: "gcr.io/p/site:1",
152
+ projectId: "three-cloud",
153
+ location: "asia-northeast1",
154
+ publicAccess: true,
155
+ }) as unknown as Record<string, { entityType: string; props: Record<string, unknown> }>;
156
+ return [
157
+ ["webService", { entityType: service.entityType, props: service.props }],
158
+ ["webPublicIam", { entityType: publicIam.entityType, props: publicIam.props }],
159
+ ] as Array<[string, { entityType: string; props: Record<string, unknown> }]>;
160
+ }
161
+
162
+ beforeEach(() => {
163
+ fetchMock.mockReset();
164
+ vi.stubGlobal("fetch", fetchMock);
165
+ process.env.GCP_ENDPOINT_URL = "http://localhost:4588";
166
+ process.env.GCP_PROJECT_ID = "three-cloud";
167
+ delete process.env.GCP_REGION;
168
+ fetchMock.mockImplementation(async (url: string) => {
169
+ if (url.includes(":getIamPolicy")) {
170
+ return url.includes(policyUrl)
171
+ ? reply(200, { bindings: [{ role: "roles/run.invoker", members: ["allUsers"] }] })
172
+ : reply(404, { error: { code: 404 } });
173
+ }
174
+ return reply(200, { name: url.split("?")[0].split("/").slice(-6).join("/") });
175
+ });
176
+ });
177
+
178
+ afterEach(() => {
179
+ vi.unstubAllGlobals();
180
+ delete process.env.GCP_ENDPOINT_URL;
181
+ delete process.env.GCP_PROJECT_ID;
182
+ });
183
+
184
+ for (const order of ["service first", "binding first"]) {
185
+ test(`webPublicIam is observed with a physical id, ${order}`, async () => {
186
+ const pairs = composite();
187
+ const entities = new Map(order === "service first" ? pairs : [...pairs].reverse());
188
+ const out = await describeResources({ environment: "local", buildOutput: "", entityNames: [...entities.keys()], entities });
189
+ expect(out.unobserved?.webPublicIam).toBeUndefined();
190
+ expect(out.resources.webPublicIam?.physicalId).toBe(
191
+ "projects/three-cloud/locations/asia-northeast1/services/three-cloud-site#roles/run.invoker#allUsers",
192
+ );
193
+ });
194
+ }
195
+ });