@deployfoundation/foundation-deploy 0.2.53 → 0.2.55

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 CHANGED
@@ -55,7 +55,7 @@ bun run packages/infra/bin/foundation-deploy.ts <command> --instance <path>
55
55
  | `slack-manifest` | Print the instance's Slack app manifest as JSON (the Slack CLI's `get-manifest` hook contract). |
56
56
  | `stage-customization` | Validate and stage a tenant-owned runtime config. |
57
57
  | `crm-key` | Write the company CRM's `crm/1` key, piped in with `--stdin`, into `<secretPrefix>/crm/http` (`crm.provider` kind `http`). |
58
- | `crm-check` | Run the `crm/1` conformance suite: the read cases against the instance's CRM with its stored key, or, with `--base-url --tenant --key-stdin` and no instance, any server, write cases included. |
58
+ | `crm-check` | Run the `crm/1` conformance suite: the read cases against the instance's CRM with its stored key, or, with `--base-url --tenant --key-stdin` and no instance, any server, write cases included, every case strict given the server's harness endpoint (`--harness-url`) and the deployment's inputs (docs/crm-provider-contract.md#crm-check). A CRM can also import the suite: `@deployfoundation/foundation-deploy/crm-conformance`. |
59
59
  | `crm-replay` | Replay the agent's CRM writes (JSONL) into the instance's own table after a rollback to the DynamoDB provider. |
60
60
 
61
61
  `--instance <path>` is required for every command (or `FOUNDATION_INSTANCE_FILE`
package/dist/bin/app.js CHANGED
@@ -8,16 +8,16 @@ import {
8
8
  FoundationPages,
9
9
  FoundationPipeline,
10
10
  NewsletterStack
11
- } from "../chunk-spq5kr1t.js";
11
+ } from "../chunk-68jxyg2n.js";
12
12
  import"../chunk-pxhftba1.js";
13
- import"../chunk-dz91avrb.js";
14
13
  import {
15
14
  adminsFor,
16
15
  instanceNames,
17
16
  loadInstanceFile,
18
17
  provisionsIntegration
19
- } from "../chunk-fjdgxw8h.js";
20
- import"../chunk-96fjaysn.js";
18
+ } from "../chunk-pmjbzsq3.js";
19
+ import"../chunk-dz91avrb.js";
20
+ import"../chunk-rvx0cncy.js";
21
21
  import"../chunk-5wgwscd0.js";
22
22
 
23
23
  // bin/app.ts
@@ -1,17 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
- import {
4
- RELEASE_BUCKET_ENV,
5
- RELEASE_VERSION_RE,
6
- manifestKey,
7
- releaseCacheDir,
8
- skillsKey,
9
- verifyManifest
10
- } from "../chunk-pxhftba1.js";
11
- import {
12
- DEFAULT_RELEASE_BUCKET,
13
- expectedKeyArn
14
- } from "../chunk-dz91avrb.js";
15
3
  import {
16
4
  argv,
17
5
  aws,
@@ -32,16 +20,28 @@ import {
32
20
  secretExists,
33
21
  stackExists,
34
22
  stackOutput
35
- } from "../chunk-nvsj7bbw.js";
23
+ } from "../chunk-345e0grd.js";
24
+ import {
25
+ RELEASE_BUCKET_ENV,
26
+ RELEASE_VERSION_RE,
27
+ manifestKey,
28
+ releaseCacheDir,
29
+ skillsKey,
30
+ verifyManifest
31
+ } from "../chunk-pxhftba1.js";
36
32
  import {
37
33
  CUSTOMIZATION_ARTIFACT_NAME,
38
34
  instanceNames,
39
35
  mongoCollectionPolicyEnv,
40
36
  slackCommandPrefix,
41
37
  versionedConfigKey
42
- } from "../chunk-fjdgxw8h.js";
43
- import"../chunk-m76jt5rc.js";
44
- import"../chunk-3614v9rr.js";
38
+ } from "../chunk-pmjbzsq3.js";
39
+ import {
40
+ DEFAULT_RELEASE_BUCKET,
41
+ expectedKeyArn
42
+ } from "../chunk-dz91avrb.js";
43
+ import"../chunk-hvntk8p5.js";
44
+ import"../chunk-xt9gdsa0.js";
45
45
  import {
46
46
  BASE_SLACK_EVENTS,
47
47
  CAPABILITY_IDS,
@@ -53,7 +53,7 @@ import {
53
53
  requiredGithubPermissions,
54
54
  requiredSlackCommands,
55
55
  requiredSlackScopes
56
- } from "../chunk-96fjaysn.js";
56
+ } from "../chunk-rvx0cncy.js";
57
57
  import {
58
58
  __require
59
59
  } from "../chunk-5wgwscd0.js";
@@ -2449,6 +2449,20 @@ crm-check options
2449
2449
  --write with --base-url only: also run the write cases, which
2450
2450
  create records and spend the week's outreach allowance;
2451
2451
  point it at a sandbox tenant
2452
+ with --base-url --write, what the remaining write cases need:
2453
+ --harness-url <url> the CRM's harness endpoint (crm-harness/1), which does
2454
+ --harness-key-stdin what only a person can; its key comes on stdin
2455
+ --readonly-key-stdin a key for the same tenant without write scopes, on stdin
2456
+ --status-policy <file>
2457
+ the deployment's capabilities.crm.statusPolicy (JSON
2458
+ or YAML)
2459
+ --suppressed-statuses <file>
2460
+ its capabilities.crm.outreach.suppressedStatuses
2461
+ --spend-weekly-allowance
2462
+ drive this week's outreach caps to their limits
2463
+ (a throwaway database only)
2464
+ Keys on stdin go one per line, in this order: the crm/1 key, the read-only
2465
+ key, the harness key (each only when its flag is given).
2452
2466
 
2453
2467
  crm-replay options
2454
2468
  --from <file> REQUIRED: the writes to replay, one JSON object per line
@@ -2462,6 +2476,22 @@ function flag2(args, name) {
2462
2476
  const index = args.indexOf(name);
2463
2477
  return index === -1 ? undefined : args[index + 1];
2464
2478
  }
2479
+ function valueFlag(args, name, what) {
2480
+ if (!args.includes(name))
2481
+ return;
2482
+ const value = flag2(args, name);
2483
+ if (value === undefined || value.startsWith("-"))
2484
+ throw new Error(`${name} needs ${what}`);
2485
+ return value;
2486
+ }
2487
+ var CRM_CHECK_TARGET_FLAGS = [
2488
+ "--harness-url",
2489
+ "--harness-key-stdin",
2490
+ "--readonly-key-stdin",
2491
+ "--status-policy",
2492
+ "--suppressed-statuses",
2493
+ "--spend-weekly-allowance"
2494
+ ];
2465
2495
  function contextFor(args) {
2466
2496
  const paths = loadInstanceContext(resolveInstanceFilePath(args, process.env));
2467
2497
  return awsContext({
@@ -2540,12 +2570,22 @@ ${USAGE}`);
2540
2570
  const tenantId = flag2(args, "--tenant");
2541
2571
  if (baseUrl === undefined || tenantId === undefined || !args.includes("--key-stdin"))
2542
2572
  throw new Error("crm-check --base-url needs --tenant <id> and --key-stdin");
2543
- const { crmCheckTarget, defaultCrmCommandDeps } = await import("../chunk-1pww63wd.js");
2573
+ const harnessUrl = valueFlag(args, "--harness-url", "the CRM's harness endpoint URL");
2574
+ if (harnessUrl !== undefined !== args.includes("--harness-key-stdin"))
2575
+ throw new Error("crm-check --harness-url <url> and --harness-key-stdin go together: the harness key comes on stdin");
2576
+ const statusPolicyFile = valueFlag(args, "--status-policy", "a file");
2577
+ const suppressedStatusesFile = valueFlag(args, "--suppressed-statuses", "a file");
2578
+ const { crmCheckTarget, defaultCrmCommandDeps } = await import("../chunk-ztrhakgc.js");
2544
2579
  return crmCheckTarget({
2545
2580
  baseUrl,
2546
2581
  tenantId,
2547
2582
  write: args.includes("--write"),
2548
- allowSkips: args.includes("--allow-skips")
2583
+ allowSkips: args.includes("--allow-skips"),
2584
+ readOnlyKeyStdin: args.includes("--readonly-key-stdin"),
2585
+ spendWeeklyAllowance: args.includes("--spend-weekly-allowance"),
2586
+ ...harnessUrl === undefined ? {} : { harnessUrl },
2587
+ ...statusPolicyFile === undefined ? {} : { statusPolicyFile },
2588
+ ...suppressedStatusesFile === undefined ? {} : { suppressedStatusesFile }
2549
2589
  }, defaultCrmCommandDeps());
2550
2590
  }
2551
2591
  if (command === "github-app-create") {
@@ -2613,21 +2653,24 @@ ${USAGE}`);
2613
2653
  return 0;
2614
2654
  }
2615
2655
  case "crm-key": {
2616
- const { crmKey, defaultCrmCommandDeps } = await import("../chunk-1pww63wd.js");
2656
+ const { crmKey, defaultCrmCommandDeps } = await import("../chunk-ztrhakgc.js");
2617
2657
  await crmKey(ctx, { stdin: args.includes("--stdin") }, defaultCrmCommandDeps());
2618
2658
  return 0;
2619
2659
  }
2620
2660
  case "crm-check": {
2621
2661
  if (args.includes("--write"))
2622
2662
  throw new Error("crm-check --write creates records and spends the week's outreach allowance, so it never runs against the instance's own tenant: use --base-url with a sandbox tenant");
2623
- const { crmCheck, defaultCrmCommandDeps } = await import("../chunk-1pww63wd.js");
2663
+ const targetOnly = CRM_CHECK_TARGET_FLAGS.filter((name) => args.includes(name));
2664
+ if (targetOnly.length > 0)
2665
+ throw new Error(`crm-check --instance runs the read cases with the deployment's own key and policy; ${targetOnly.join(", ")} feed the write cases: use --base-url --write with a sandbox tenant`);
2666
+ const { crmCheck, defaultCrmCommandDeps } = await import("../chunk-ztrhakgc.js");
2624
2667
  return crmCheck(ctx, { allowSkips: args.includes("--allow-skips") }, defaultCrmCommandDeps());
2625
2668
  }
2626
2669
  case "crm-replay": {
2627
2670
  const from = flag2(args, "--from");
2628
2671
  if (from === undefined || from.startsWith("-"))
2629
2672
  throw new Error("crm-replay needs --from <file>: the writes to replay, as JSONL");
2630
- const { crmReplay, defaultCrmCommandDeps } = await import("../chunk-1pww63wd.js");
2673
+ const { crmReplay, defaultCrmCommandDeps } = await import("../chunk-ztrhakgc.js");
2631
2674
  const result = await crmReplay(ctx, { from }, defaultCrmCommandDeps());
2632
2675
  return result.refused.length > 0 ? 1 : 0;
2633
2676
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  instanceNames,
3
3
  loadInstanceFile
4
- } from "./chunk-fjdgxw8h.js";
4
+ } from "./chunk-pmjbzsq3.js";
5
5
 
6
6
  // src/deploy/aws.ts
7
7
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
@@ -17,7 +17,7 @@ import {
17
17
  provisionsIntegration,
18
18
  requiresAuthenticatedQueue,
19
19
  slackCommandPrefixes
20
- } from "./chunk-fjdgxw8h.js";
20
+ } from "./chunk-pmjbzsq3.js";
21
21
  import {
22
22
  capabilityScheduledJobs,
23
23
  crmOutreachConfigured,
@@ -28,7 +28,7 @@ import {
28
28
  parseSchedule,
29
29
  scheduledJobBindings,
30
30
  webhookJobBindings
31
- } from "./chunk-96fjaysn.js";
31
+ } from "./chunk-rvx0cncy.js";
32
32
 
33
33
  // src/stacks/agent-stack.ts
34
34
  import * as cdk from "aws-cdk-lib";
@@ -1942,8 +1942,10 @@ function addConfigLoadAlarm(stack, args) {
1942
1942
  `${args.instance.displayName}'s runtime is not serving the config deployed for it: the`,
1943
1943
  "config failed to load, so it runs on its last good config with company-data",
1944
1944
  "capabilities off, or on the built-in fallback. The config_fallback_active or",
1945
- "config_reload_failed line in the runtime's live log group names the refused field.",
1946
- "Fix config.yaml, run foundation-deploy check, then deploy or config:sync."
1945
+ "config_reload_failed line in the runtime's live log group says why: its error_message",
1946
+ "names the refused field (record keys shown as *), the YAML error and line, or the",
1947
+ "S3/KMS error and the IAM action it cites (error_type s3_read_failed).",
1948
+ "Fix config.yaml or the grant, run foundation-deploy check, then deploy or config:sync."
1947
1949
  ].join(" "),
1948
1950
  metric: filter.metric({ statistic: "Sum", period: cdk2.Duration.hours(1) }),
1949
1951
  threshold: 1,
@@ -2736,6 +2738,13 @@ class FoundationNetwork extends cdk5.Stack {
2736
2738
  actions: ["s3:GetObject"],
2737
2739
  resources: [`arn:${this.partition}:s3:::prod-${this.region}-starport-layer-bucket/*`]
2738
2740
  }));
2741
+ s3Endpoint.addToPolicy(new iam6.PolicyStatement({
2742
+ sid: "AmplifyArtifactsBucket",
2743
+ effect: iam6.Effect.ALLOW,
2744
+ principals: [new iam6.AnyPrincipal],
2745
+ actions: ["s3:GetObject"],
2746
+ resources: [`arn:${this.partition}:s3:::aws-amplify-prod-${this.region}-artifacts/*`]
2747
+ }));
2739
2748
  }
2740
2749
  if (!skipEndpoints.has("dynamodb")) {
2741
2750
  const dynamoEndpoint = this.vpc.addGatewayEndpoint("DynamoDbEndpoint", {
@@ -15,7 +15,7 @@ import {
15
15
  classifyOutreach,
16
16
  movesToDraftedStatus,
17
17
  outreachWeekStart
18
- } from "./chunk-p7zkm4pp.js";
18
+ } from "./chunk-caqstrxb.js";
19
19
  import {
20
20
  CRM_MAX_ACTIVITY_PAGE_SIZE,
21
21
  CRM_MAX_CURSOR_CHARS,
@@ -29,8 +29,8 @@ import {
29
29
  parseCrmOutreachQueueResponse,
30
30
  parseCrmSearchResponse,
31
31
  parseCrmTimestamp
32
- } from "./chunk-3614v9rr.js";
33
- import"./chunk-96fjaysn.js";
32
+ } from "./chunk-xt9gdsa0.js";
33
+ import"./chunk-rvx0cncy.js";
34
34
  import"./chunk-5wgwscd0.js";
35
35
 
36
36
  // ../capability-crm/src/proxy/repository.ts
@@ -5,7 +5,7 @@ import {
5
5
  CRM_OUTREACH_REPLY_ACTIVITY,
6
6
  CRM_OUTREACH_SENT_ACTIVITY,
7
7
  CrmInputError
8
- } from "./chunk-3614v9rr.js";
8
+ } from "./chunk-xt9gdsa0.js";
9
9
 
10
10
  // ../capability-crm/src/proxy/policy.ts
11
11
  class CrmPolicyRefusedError extends Error {
@@ -0,0 +1,101 @@
1
+ import {
2
+ CrmConformanceHarnessError
3
+ } from "./chunk-vcz9zbb3.js";
4
+ import {
5
+ CRM_HTTP_HEADERS,
6
+ crmHttpBaseUrl,
7
+ isCrmHttpKey,
8
+ readCrmHttpBody
9
+ } from "./chunk-hvntk8p5.js";
10
+ import"./chunk-caqstrxb.js";
11
+ import"./chunk-xt9gdsa0.js";
12
+ import"./chunk-rvx0cncy.js";
13
+ import"./chunk-5wgwscd0.js";
14
+
15
+ // ../capability-crm/src/contract/http-harness.ts
16
+ var CRM_HARNESS_CONTRACT = "crm-harness/1";
17
+ var USER_AGENT = "foundation-crm-conformance/1";
18
+ var TIMEOUT_MS = 1e4;
19
+ var MAX_REPLY_BYTES = 64 * 1024;
20
+ function crmHarnessUrl(value) {
21
+ try {
22
+ return crmHttpBaseUrl(value);
23
+ } catch {
24
+ throw new Error("the harness URL must be an HTTPS URL with no userinfo, query or fragment");
25
+ }
26
+ }
27
+ function createCrmHttpHarness(options) {
28
+ const url = crmHarnessUrl(options.url);
29
+ if (!isCrmHttpKey(options.apiKey)) {
30
+ throw new Error("the harness key must be 16 to 1024 visible ASCII characters");
31
+ }
32
+ const { apiKey, tenantId } = options;
33
+ const fetchImpl = options.fetchImpl ?? fetch;
34
+ async function call(request) {
35
+ const signal = AbortSignal.timeout(TIMEOUT_MS);
36
+ let response;
37
+ try {
38
+ response = await fetchImpl(url, {
39
+ method: "POST",
40
+ redirect: "error",
41
+ headers: {
42
+ authorization: `Bearer ${apiKey}`,
43
+ accept: "application/json",
44
+ "content-type": "application/json",
45
+ "user-agent": USER_AGENT,
46
+ [CRM_HTTP_HEADERS.contract]: CRM_HARNESS_CONTRACT
47
+ },
48
+ body: JSON.stringify(request),
49
+ signal
50
+ });
51
+ } catch {
52
+ throw new CrmConformanceHarnessError(`${request.action} ${signal.aborted ? "did not answer in time" : "could not reach the harness"}`);
53
+ }
54
+ let text;
55
+ try {
56
+ text = await readCrmHttpBody(response, MAX_REPLY_BYTES, signal);
57
+ } catch {
58
+ throw new CrmConformanceHarnessError(`${request.action} reply is too large or unreadable`);
59
+ }
60
+ if (response.status < 200 || response.status > 299) {
61
+ throw new CrmConformanceHarnessError(`${request.action} answered HTTP ${response.status}`);
62
+ }
63
+ return text;
64
+ }
65
+ return {
66
+ async setStatusAsPerson(recordType, recordId, status) {
67
+ await call({ action: "set_status_as_person", tenantId, recordType, recordId, status });
68
+ },
69
+ async setDoNotContact(recordType, recordId) {
70
+ await call({ action: "set_do_not_contact", tenantId, recordType, recordId });
71
+ },
72
+ async suppressEmail(email) {
73
+ await call({ action: "suppress_email", tenantId, email });
74
+ },
75
+ async addPersonNote(recordType, recordId, summary) {
76
+ const text = await call({
77
+ action: "add_person_note",
78
+ tenantId,
79
+ recordType,
80
+ recordId,
81
+ summary
82
+ });
83
+ let activityId;
84
+ try {
85
+ const reply = JSON.parse(text);
86
+ if (typeof reply === "object" && reply !== null && "activityId" in reply) {
87
+ activityId = reply.activityId;
88
+ }
89
+ } catch {}
90
+ if (typeof activityId !== "string" || !/^[\x21-\x7e]{1,256}$/.test(activityId)) {
91
+ throw new CrmConformanceHarnessError("add_person_note answered no activityId");
92
+ }
93
+ return activityId;
94
+ }
95
+ };
96
+ }
97
+ export {
98
+ CRM_HARNESS_CONTRACT,
99
+ createCrmHttpHarness,
100
+ crmHarnessUrl
101
+ };
@@ -12,7 +12,7 @@ import {
12
12
  parseCrmOutreachState,
13
13
  parseCrmProviderMeta,
14
14
  parseCrmSearchResponse
15
- } from "./chunk-3614v9rr.js";
15
+ } from "./chunk-xt9gdsa0.js";
16
16
 
17
17
  // ../connectors/src/crm-http.ts
18
18
  import { randomUUID } from "node:crypto";
@@ -0,0 +1,13 @@
1
+ import {
2
+ CrmConformanceHarnessError,
3
+ runCrmConformance
4
+ } from "./chunk-vcz9zbb3.js";
5
+ import"./chunk-hvntk8p5.js";
6
+ import"./chunk-caqstrxb.js";
7
+ import"./chunk-xt9gdsa0.js";
8
+ import"./chunk-rvx0cncy.js";
9
+ import"./chunk-5wgwscd0.js";
10
+ export {
11
+ CrmConformanceHarnessError,
12
+ runCrmConformance
13
+ };
@@ -2,7 +2,7 @@ import {
2
2
  INTEGRATION_CAPABILITY_IDS,
3
3
  PROACTIVE_CHANNEL_MODES,
4
4
  SLACK_COMMAND_PREFIX_RE
5
- } from "./chunk-96fjaysn.js";
5
+ } from "./chunk-rvx0cncy.js";
6
6
 
7
7
  // ../core/src/instance.ts
8
8
  import { parse as parseYaml } from "yaml";
@@ -1,8 +1,3 @@
1
- // ../core/src/config.ts
2
- import { createHash as createHash2 } from "node:crypto";
3
- import { parse as parseYaml } from "yaml";
4
- import { z as z4 } from "zod";
5
-
6
1
  // ../core/src/capabilities.ts
7
2
  import { z } from "zod";
8
3
 
@@ -936,6 +931,11 @@ function requiredSlackCommandSuffixes(enabled) {
936
931
  return [...suffixes].sort();
937
932
  }
938
933
 
934
+ // ../core/src/config.ts
935
+ import { createHash as createHash2 } from "node:crypto";
936
+ import { parse as parseYaml } from "yaml";
937
+ import { z as z4 } from "zod";
938
+
939
939
  // ../core/src/jobs.ts
940
940
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
941
941
  import { z as z3 } from "zod";
@@ -1234,6 +1234,33 @@ function parseInstanceConfig(yamlText) {
1234
1234
  throw new Error(`job ${job.id} owner ${job.owner} must be an instance admin`);
1235
1235
  return cfg;
1236
1236
  }
1237
+ var ZOD_ISSUE_CODES = new Set(Object.values(z4.ZodIssueCode));
1238
+ var ZOD_PARSED_TYPES = new Set(Object.values(z4.ZodParsedType));
1239
+ var YAML_ERROR_CODES = new Set([
1240
+ "ALIAS_PROPS",
1241
+ "BAD_ALIAS",
1242
+ "BAD_COLLECTION_TYPE",
1243
+ "BAD_DIRECTIVE",
1244
+ "BAD_DQ_ESCAPE",
1245
+ "BAD_INDENT",
1246
+ "BAD_PROP_ORDER",
1247
+ "BAD_SCALAR_START",
1248
+ "BLOCK_AS_IMPLICIT_KEY",
1249
+ "BLOCK_IN_FLOW",
1250
+ "DUPLICATE_KEY",
1251
+ "IMPOSSIBLE",
1252
+ "KEY_OVER_1024_CHARS",
1253
+ "MISSING_CHAR",
1254
+ "MULTILINE_IMPLICIT_KEY",
1255
+ "MULTIPLE_ANCHORS",
1256
+ "MULTIPLE_DOCS",
1257
+ "MULTIPLE_TAGS",
1258
+ "NON_STRING_KEY",
1259
+ "RESOURCE_EXHAUSTION",
1260
+ "TAB_AS_INDENT",
1261
+ "TAG_RESOLVE_FAILED",
1262
+ "UNEXPECTED_TOKEN"
1263
+ ]);
1237
1264
 
1238
1265
  // ../core/src/logger.ts
1239
1266
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -1295,4 +1322,4 @@ function wrapUntrusted(source, content) {
1295
1322
  ${safe}
1296
1323
  </${UNTRUSTED_TAG}>`;
1297
1324
  }
1298
- export { parseSchedule, BASE_SLACK_EVENTS, SLACK_COMMAND_PREFIX_RE, CAPABILITY_IDS, INTEGRATION_CAPABILITY_IDS, requiredGithubPermissions, requiredSlackScopes, requiredSlackCommands, isScheduledJob, isWebhookJob, webhookJobBindings, scheduledJobBindings, jobWebhookKey, capabilityScheduledJobs, crmOutreachConfigured, crmPolicyFingerprint, parseInstanceConfig, PROACTIVE_CHANNEL_MODES, encodeProactiveChannels, wrapUntrusted };
1325
+ export { parseSchedule, RawCrmStatusPolicy, RawCrmStatusesByType, BASE_SLACK_EVENTS, SLACK_COMMAND_PREFIX_RE, CAPABILITY_IDS, INTEGRATION_CAPABILITY_IDS, requiredGithubPermissions, requiredSlackScopes, requiredSlackCommands, isScheduledJob, isWebhookJob, webhookJobBindings, scheduledJobBindings, jobWebhookKey, capabilityScheduledJobs, crmOutreachConfigured, crmPolicyFingerprint, parseInstanceConfig, PROACTIVE_CHANNEL_MODES, encodeProactiveChannels, wrapUntrusted };
@@ -6,21 +6,26 @@ import {
6
6
  createCrmHttpClient,
7
7
  encodeCrmActor,
8
8
  readCrmHttpBody
9
- } from "./chunk-m76jt5rc.js";
9
+ } from "./chunk-hvntk8p5.js";
10
10
  import {
11
11
  outreachWeekStart,
12
12
  personOwnedStatuses
13
- } from "./chunk-p7zkm4pp.js";
13
+ } from "./chunk-caqstrxb.js";
14
14
  import {
15
15
  CRM_NOTE_ACTIVITY,
16
16
  CRM_OUTREACH_DRAFTED_ACTIVITY,
17
17
  CRM_REQUIRED_PROVIDER_OPERATIONS
18
- } from "./chunk-3614v9rr.js";
19
- import"./chunk-96fjaysn.js";
20
- import"./chunk-5wgwscd0.js";
18
+ } from "./chunk-xt9gdsa0.js";
21
19
 
22
20
  // ../capability-crm/src/contract/conformance.ts
23
21
  import { randomUUID } from "node:crypto";
22
+ class CrmConformanceHarnessError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "CrmConformanceHarnessError";
26
+ }
27
+ }
28
+
24
29
  class Skip extends Error {
25
30
  reason;
26
31
  needs;
@@ -386,9 +391,11 @@ class Suite {
386
391
  return;
387
392
  }
388
393
  async transitionCase() {
389
- const transition = this.target.disallowedTransition;
390
- if (transition === undefined)
391
- throw new Skip("no disallowedTransition given");
394
+ const given = this.target.disallowedTransition;
395
+ const transition = given ?? this.policyDisallowedTransition();
396
+ if (transition === undefined) {
397
+ throw new Skip(this.target.statusPolicy === undefined ? "no disallowedTransition or statusPolicy given" : "the statusPolicy allows every move from a new record's status");
398
+ }
392
399
  const fixture = await this.fixture("transition");
393
400
  const recordId = transition.recordType === "company" ? fixture.companyId : fixture.contactId;
394
401
  const current = await this.profile(transition.recordType, recordId);
@@ -398,6 +405,23 @@ class Suite {
398
405
  status: transition.to,
399
406
  idempotencyKey: this.key()
400
407
  }, { status: current.status, statusVersion: current.statusVersion ?? 0 }), "refused", "transition_not_allowed");
408
+ return given === undefined ? `${transition.recordType} → ${transition.to}, from the statusPolicy` : undefined;
409
+ }
410
+ policyDisallowedTransition() {
411
+ const policy = this.target.statusPolicy;
412
+ if (policy === undefined)
413
+ return;
414
+ const meta = this.requireMeta();
415
+ for (const recordType of ["company", "contact"]) {
416
+ const rule = policy[recordType];
417
+ const [first, ...others] = meta.statuses[recordType];
418
+ if (rule === undefined || first === undefined)
419
+ continue;
420
+ const exits = Object.hasOwn(rule.agentTransitions, first) ? rule.agentTransitions[first] ?? [] : [];
421
+ const to = others.find((status) => !exits.includes(status));
422
+ if (to !== undefined)
423
+ return { recordType, to };
424
+ }
401
425
  return;
402
426
  }
403
427
  async statusLockedCase() {
@@ -1102,6 +1126,8 @@ async function expectError(call, code, reason) {
1102
1126
  function describe(error) {
1103
1127
  if (error instanceof Failure)
1104
1128
  return error.message;
1129
+ if (error instanceof CrmConformanceHarnessError)
1130
+ return `harness: ${error.message}`;
1105
1131
  if (error instanceof CrmHttpError) {
1106
1132
  return [
1107
1133
  error.code,
@@ -1112,6 +1138,5 @@ function describe(error) {
1112
1138
  }
1113
1139
  return `unexpected ${error instanceof Error ? error.name : typeof error}`;
1114
1140
  }
1115
- export {
1116
- runCrmConformance
1117
- };
1141
+
1142
+ export { CrmConformanceHarnessError, runCrmConformance };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  wrapUntrusted
3
- } from "./chunk-96fjaysn.js";
3
+ } from "./chunk-rvx0cncy.js";
4
4
 
5
5
  // ../connectors/src/crm.ts
6
6
  var CRM_MAX_BATCH_SIZE = 100;