@salesforce/afv-skills 1.17.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/skills/analyzing-test-failures/SKILL.md +159 -0
  3. package/skills/building-sf-integrations/SKILL.md +1 -1
  4. package/skills/checking-devops-prerequisites/SKILL.md +141 -0
  5. package/skills/configuring-code-analyzer/SKILL.md +482 -0
  6. package/skills/configuring-code-analyzer/examples/apex-project-config.yml +41 -0
  7. package/skills/configuring-code-analyzer/examples/ci-github-actions.yml +96 -0
  8. package/skills/configuring-code-analyzer/examples/fullstack-project-config.yml +46 -0
  9. package/skills/configuring-code-analyzer/examples/lwc-project-config.yml +26 -0
  10. package/skills/configuring-code-analyzer/references/ci-cd-templates.md +648 -0
  11. package/skills/configuring-code-analyzer/references/config-schema.md +257 -0
  12. package/skills/configuring-code-analyzer/references/diagnostic-flow.md +70 -0
  13. package/skills/configuring-code-analyzer/references/engine-prerequisites.md +276 -0
  14. package/skills/configuring-code-analyzer/references/rule-name-resolution.md +67 -0
  15. package/skills/configuring-code-analyzer/references/troubleshooting.md +298 -0
  16. package/skills/configuring-code-analyzer/scripts/check-prerequisites.sh +189 -0
  17. package/skills/configuring-code-analyzer/scripts/generate-config.sh +143 -0
  18. package/skills/configuring-code-analyzer/scripts/validate-config.sh +153 -0
  19. package/skills/configuring-quality-gate/SKILL.md +120 -0
  20. package/skills/configuring-test-provider/SKILL.md +113 -0
  21. package/skills/creating-fix-work-item/SKILL.md +66 -0
  22. package/skills/managing-cdc-enablement/SKILL.md +164 -0
  23. package/skills/managing-cdc-enablement/assets/PlatformEventChannel-template.xml +5 -0
  24. package/skills/managing-cdc-enablement/assets/PlatformEventChannelMember-template.xml +11 -0
  25. package/skills/managing-cdc-enablement/references/deploy-troubleshooting.md +73 -0
  26. package/skills/managing-cdc-enablement/references/filter-expressions.md +93 -0
  27. package/skills/managing-suite-assignments/SKILL.md +161 -0
  28. package/skills/polling-test-results/SKILL.md +72 -0
  29. package/skills/recommending-devops-tests/SKILL.md +137 -0
  30. package/skills/running-code-analyzer/SKILL.md +264 -267
  31. package/skills/running-code-analyzer/references/post-scan-workflows.md +286 -0
  32. package/skills/running-code-analyzer/scripts/describe-rule.js +382 -0
  33. package/skills/running-code-analyzer/scripts/list-rules.js +260 -0
  34. package/skills/running-code-analyzer/scripts/query-results.js +230 -0
  35. package/skills/running-devops-test-suite/SKILL.md +144 -0
  36. package/skills/syncing-test-providers/SKILL.md +108 -0
  37. package/skills/using-salesforce-archive/SKILL.md +121 -0
  38. package/skills/using-salesforce-archive/examples/monitor-failed-jobs.md +47 -0
  39. package/skills/using-salesforce-archive/references/archive-activity-entity.md +59 -0
  40. package/skills/using-salesforce-archive/references/connect-api-operations.md +157 -0
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: syncing-test-providers
3
+ description: "Re-syncs a configured DevOps Center test provider on a pipeline to pick up new test suites, via the Connect API sync endpoint, after explicit user confirmation. Takes a pipelineId and one or more testProviderIds and triggers an asynchronous re-sync so newly added suites become available for assignment. Use this skill when a test provider (e.g. Apex Unit Tests, Code Analyzer, Flow Tests, Provar) has had suites added since it was last configured and the user wants those suites to show up in DevOps Center. TRIGGER when: the user wants to re-sync a test provider, pull in new suites from a provider, refresh a provider's suite list, or says assigned suites are missing/out of date for a configured provider. DO NOT TRIGGER when: configuring a provider for the first time (that creates a new DevopsPipelineTestProvider configuration), or assigning/mapping existing suites to a stage (use managing-suite-assignments)."
4
+ metadata:
5
+ version: "1.0"
6
+ minApiVersion: "67.0"
7
+ ---
8
+
9
+ # Syncing Test Providers
10
+
11
+ Re-syncs a configured test provider on a DevOps Center pipeline so that suites added to the provider since it was last configured become available for assignment to stages.
12
+
13
+ **Confirmation required:** Yes — explicit confirmation before the sync is triggered.
14
+
15
+ ## Prerequisites
16
+
17
+ Load `checking-devops-prerequisites` first — Prerequisites 1–4 (org login, Agentforce DX plugin, DevOps Center org auth, pipeline identified). Prerequisite 5 (stage) is **not** required: providers are synced at the pipeline level, not the stage level.
18
+
19
+ | Variable | Source |
20
+ |---|---|
21
+ | `doce-org-alias` | Established in Prerequisite 1 |
22
+ | `pipelineId` | Identified in Prerequisite 4 (pipeline selection) |
23
+ | `testProviderId` | Resolved by fetching the pipeline's test providers (below) |
24
+
25
+ ---
26
+
27
+ ## Step 1 — Fetch test providers to resolve the provider ID
28
+
29
+ Get all test providers configured on the pipeline so you can resolve the `testProviderId` and confirm the provider name with the user:
30
+
31
+ ```bash
32
+ sf api request rest \
33
+ "/services/data/v67.0/connect/devopstesting/pipeline/<pipelineId>/testProviders?status=all" \
34
+ --target-org <doce-org-alias>
35
+ ```
36
+
37
+ Each provider entry includes `testProviderId`, `testProviderName`, and a status (Configured vs. Available). Present a short summary grouped by status:
38
+
39
+ ```text
40
+ Test providers for <pipelineName>:
41
+
42
+ ✓ Configured:
43
+ - Code Analyzer (63 suites)
44
+ - Apex Unit Tests (5 suites)
45
+
46
+ Available (not yet configured):
47
+ - Flow Tests
48
+ ```
49
+
50
+ - **Only a Configured provider can be synced.** If the user names an *Available* (not-yet-configured) provider, explain it must be configured first — this skill does not configure providers.
51
+ - If the pipeline has no configured providers, report that and stop — do NOT fabricate a provider or ID.
52
+
53
+ ## Step 2 — Confirmation gate
54
+
55
+ **Required — do not call the API before the user confirms.**
56
+
57
+ > "I'll re-sync `<testProviderName>` on the `<pipelineName>` pipeline to pick up any new suites. Confirm?"
58
+
59
+ Do not proceed until the user gives an affirmative response.
60
+
61
+ ## Step 3 — Trigger the sync
62
+
63
+ On confirmation, call the sync endpoint with the provider ID(s) and pipeline ID:
64
+
65
+ ```bash
66
+ sf api request rest \
67
+ "/services/data/v67.0/connect/devops/sync" \
68
+ --method POST \
69
+ --body '{
70
+ "testProviderIds": ["<testProviderId>"],
71
+ "pipelineId": "<pipelineId>"
72
+ }' \
73
+ --target-org <doce-org-alias>
74
+ ```
75
+
76
+ `testProviderIds` is a list — multiple configured providers can be synced in one call.
77
+
78
+ ## On success
79
+
80
+ > "Provider `<testProviderName>` sync started. The operation is running asynchronously — new suites will be available shortly."
81
+
82
+ The sync runs asynchronously; newly synced suites can then be assigned to stages with `managing-suite-assignments`.
83
+
84
+ ---
85
+
86
+ ## Critical gotcha
87
+
88
+ **Do NOT use** `POST /connect/devops/pipeline/<pipelineId>/testProvider` to sync — that endpoint **creates a new provider configuration** and will result in duplicate `DevopsPipelineTestProvider` records. Sync only via `POST /connect/devops/sync`.
89
+
90
+ ## Error Handling
91
+
92
+ Never expose raw API error messages, stack traces, or JSON payloads to the user. Map response status codes to plain-language messages:
93
+
94
+ | Status | User-facing message |
95
+ |---|---|
96
+ | 400 | "The sync request was invalid. Check that the provider ID and pipeline ID are correct." |
97
+ | 403 | "You don't have permission to sync test providers on this pipeline." |
98
+ | 404 | "The pipeline or test provider was not found." |
99
+ | 500 | "A server error occurred. Try again in a few minutes." |
100
+
101
+ ---
102
+
103
+ ## Related skills
104
+
105
+ - **`checking-devops-prerequisites`** — loaded first to establish org and pipeline context.
106
+ - **`configuring-test-provider`** — use to configure an available provider for the first time; this skill only re-syncs already-configured providers.
107
+ - **`managing-suite-assignments`** — after a sync surfaces new suites, use this to assign or map them to a pipeline stage.
108
+ - **`recommending-devops-tests`** — to recommend which of the newly synced suites to run.
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: using-salesforce-archive
3
+ description: "ALWAYS USE THIS SKILL for anything involving Salesforce Archive (also called Trusted Services Archive) — search, view, unarchive, analyze, mask, and erase (RTBF) archived records via the Archive Connect API, and reading archive job status from the ArchiveActivity object. TRIGGER when: user mentions Salesforce Archive, Trusted Services Archive, archive/unarchive records, ArchiveActivity, archive jobs, archive policy, archive analyzer, archived record search, archive storage, archive failure logs, right to be forgotten / RTBF on archived data, or masking archived PII — including phrasings like 'find records that were archived', 'restore archived data', 'why did the archive job fail', 'download the archive failure log', or 'monitor my archive jobs', AND even when they ask you to explain, give guidance, or write a runbook/doc about these topics rather than run code. SKIP when: the user wants generic data-export/backup unrelated to the Archive add-on, or wants to build the archive policy UI metadata."
4
+ metadata:
5
+ version: "1.0"
6
+ ---
7
+
8
+ # Salesforce Archive
9
+
10
+ Operate Salesforce Archive (also called Trusted Services Archive) through its Connect API and the `ArchiveActivity` job-metadata object. This skill covers how to search and restore archived records, run the analyzer, handle RTBF erasure and PII masking, check storage, and — the part most often missed — how to read archive job status from `ArchiveActivity` and use a job's Id + Type to download its logs.
11
+
12
+ ## Scope
13
+
14
+ - **In scope**: Calling the Archive Connect API operations under `/platform/data-resilience/archive/`; querying the `ArchiveActivity` object via SOQL/Connect; correlating a job's `ArchiveActivity` record with its log-download endpoints; the verify-after-write pattern for each async operation.
15
+ - **Out of scope**: Defining archive policies / `ArchivePolicyDefinition` metadata; building UI; generating Flows over archive data (`ArchiveActivity` is **not** Flow-queryable — see Gotchas); generic backup/export tooling unrelated to the add-on.
16
+
17
+ ---
18
+
19
+ ## Required Inputs
20
+
21
+ Gather or infer before acting:
22
+
23
+ - **Operation intent**: search, view, unarchive, analyze, mask, RTBF, storage check, or job-status/log lookup.
24
+ - **Target sObject** (`sobjectName`): required for search and unarchive.
25
+ - **Filters**: search and unarchive require `sobjectName` + at least one filter.
26
+ - **For log downloads**: the `requestId` (an `ArchiveActivity` Id, `8qv…` prefix) of a completed, log-producing job, and `reportType` = that activity's `Type`.
27
+
28
+ Preconditions (confirm or surface to the user if a call returns a not-permitted error):
29
+ - The **org** must have Salesforce Archive enabled (the `TrustedServicesArchive` / `TrustedServicesArchiveBt` org permission). Every operation is gated on this first.
30
+ - Each operation requires a **specific user permission** on top of the org gate — see the Permissions table below. There is no single "archive admin" role; access is per-capability.
31
+
32
+ ---
33
+
34
+ ## Permissions
35
+
36
+ Every operation first requires the org to have Salesforce Archive enabled (`hasTrustedServicesArchive` = `OrgPermissions.TrustedServicesArchive || TrustedServicesArchiveBt`). On top of that org gate, each capability is gated by a distinct **user permission**. A call the user isn't permitted for fails with a "not permitted" error — match the error to the missing permission below.
37
+
38
+ | Operation | User permission required |
39
+ |-----------|--------------------------|
40
+ | `search-archived-records`, `get-search-archived-records-next-page` | `ViewSearchPage` (Archive Search) — the Connect search endpoint runs the global-search path, gated by Archive Search, **not** `ViewArchivedRecords` |
41
+ | `search-archived-records-with-sharing-rules` (Agentforce) | `ViewArchivedRecords` |
42
+ | `unarchive-records` | `UnarchiveSdk` |
43
+ | `forget-archived-records` (RTBF) + `get-rtbf-status` | `Rtbf` |
44
+ | `mask-archived-records` + `get-masking-status` | `Rtbf` (masking shares the same `Rtbf` permission — **not** a separate entitlement) |
45
+ | `run-analyzer`, `get-analyzer-report`, `get-archive-storage-used` | `ArchiveAnalyzer` |
46
+ | `get-execution-details-stream-url`, `get-failed-records-stream-url` | `ViewActivitiesPage` (Archive Activities) |
47
+
48
+ > Source of truth: the Connect API resource classes in `trusted-services-archive-connect-impl` → `TrustedServicesArchiveSdkImpl` guards → `TrustedServicesArchive.accessChecks.xml`. Note `search-archived-records` routes through `performArchiverGlobalSearch` (gated by `canRunArchiveSearch` = `ViewSearchPage`); the separate Apex-SDK `searchArchivedRecords()` method is gated by `ViewArchivedRecords`, but the **Connect API** search endpoint does not use it.
49
+
50
+ ---
51
+
52
+ ## Workflow
53
+
54
+ All steps are sequential within a task. Read the referenced file the first time you touch that area.
55
+
56
+ 1. **Identify the operation and read the contract** — do not rely on general knowledge of the Archive API, which has non-obvious contracts. Load `references/connect-api-operations.md` for the exact request/response shape, required inputs, and per-operation gotchas of every Archive Connect API operation. Do this before constructing any call (e.g. `dateRanges` plural vs singular, `isSuccess` flag vs HTTP status, `url: null` meaning no log).
57
+
58
+ 2. **For job status / monitoring, read the data model** — when the task involves archive jobs, failures, progress, counts, or logs, load `references/archive-activity-entity.md` for the `ArchiveActivity` field reference and how it links to the Connect API. Query `ArchiveActivity` via SOQL or Connect — **not** Flow. For a worked end-to-end example (find failed/in-progress jobs, then pull their execution-detail and failed-records logs), load `examples/monitor-failed-jobs.md`.
59
+
60
+ 3. **Construct and send the call** — follow the contract exactly. For searches, supply `sobjectName` + ≥1 filter; for date filtering use the plural `dateRanges` array of `{field, from, to}` with full ISO-8601 datetimes.
61
+
62
+ 4. **Branch on the right signal** — some operations return HTTP 201 with a body-level success flag (`body.statusCode`, `body.isSuccess`). Read `references/connect-api-operations.md` for which signal to trust per operation; never assume the HTTP status alone means success.
63
+
64
+ 5. **Verify after every write** — re-read state to confirm the effect (see the Verify-After-Write table below). Async operations (analyzer, RTBF, masking) return a request id you must poll.
65
+
66
+ ---
67
+
68
+ ## Verify-After-Write
69
+
70
+ | After this write | Confirm by |
71
+ |------------------|-----------|
72
+ | `run-analyzer` | Poll `get-analyzer-report` until the report is populated |
73
+ | `unarchive-records` | Re-run `search-archived-records` — confirm records left the archive |
74
+ | `forget-archived-records` (RTBF) | Poll `get-rtbf-status` with the returned `request_id` |
75
+ | `mask-archived-records` | Poll `get-masking-status` with the returned `request_id` |
76
+
77
+ ---
78
+
79
+ ## Rules / Constraints
80
+
81
+ | Constraint | Rationale |
82
+ |-----------|-----------|
83
+ | Search & unarchive require `sobjectName` + at least one filter | The controller rejects an unfiltered request with "Search must be based on at least 1 field" — a full-object operation is never allowed. |
84
+ | Date filters must be full ISO-8601 datetimes (`2020-01-01T00:00:00Z`) | A date-only value (`2020-01-01`) returns `400 JSON_PARSER_ERROR` because the field is typed `xsd:dateTime`. |
85
+ | Search uses `dateRanges` (plural array); unarchive uses `dateRange` (singular) | They are genuinely different fields on the two endpoints; using the wrong shape silently drops the filter or 400s. |
86
+ | Stop pagination when `scroll_id == "-1"` | Calling `get-search-archived-records-next-page` with `"-1"` returns 500. |
87
+ | Log downloads need a real `ArchiveActivity` Id as `requestId` + that activity's `Type` as `reportType` | The backend resolves the log by the activity record; a mismatched `reportType` returns no log. |
88
+ | Never use the deprecated lookups | `global-search-by-id`, `get-global-search-results`, and `view-archived-records` are deprecated with no successor and currently 500. Use `search-archived-records` (+ next-page) instead. |
89
+ | Excluded objects are not retrievable | `Feed`, `History`, `Relation`, `Share` are not searchable; Files/Attachments are not retrievable via this API — do not promise them. |
90
+ | Query `ArchiveActivity` via SOQL/Connect, never Flow | `ArchiveActivity` has `isProcessEnabled=false`, so a Flow "Get Records" element on it fails with "You can't get ArchiveActivity records in a flow." |
91
+
92
+ ---
93
+
94
+ ## Gotchas
95
+
96
+ | Issue | Resolution |
97
+ |-------|------------|
98
+ | Treating HTTP 201 as success | Several operations return 201 with a body-level outcome. Branch on `body.statusCode` (search) or `body.isSuccess` (`with-sharing-rules`), not the HTTP code. |
99
+ | `run-analyzer.isRunning` used as a signal | It is **always** `null`; the endpoint only populates `message`. Poll `get-analyzer-report` to confirm completion instead. |
100
+ | `search-archived-records-with-sharing-rules` (Agentforce) filters as an array | `filtersJson` must be a JSON-encoded **object map** `{"Field":"Value"}`, not an array of `{field,value}`; the array form returns `isSuccess:false "No valid filters provided"`. |
101
+ | Log `url` treated as present because status is 201 | `get-*-stream-url` returns `{url}`; `url: null` means no log was resolved. Always check `url != null`. |
102
+ | Misreading `get-archive-storage-used` | `usedStorage[]`/`availableStorage[]` are parallel positional arrays: index 0=org DATA, 1=org FILE, 2=archive RECORDS, 3=archive FILE. `availableStorage[2]`/`[3]` are **always 0** (archive tier is unmetered) — that means "not tracked", not "full". |
103
+ | Expecting `ArchiveActivity` in a Flow | It is not Flow-enabled (`isProcessEnabled=false`). Use SOQL/Connect/Reports. |
104
+ | Hitting unarchive caps | Unarchive processes ≤1000 matched records per request and ≤50 requests/hour/org, and restores the whole archived hierarchy of each match. |
105
+ | RTBF/masking caps | `criteria` ≤10 entries (one per object); ≤10,000 root records/day (shared between RTBF and masking); masking is irreversible. Both RTBF and masking are gated by the same `Rtbf` user permission. |
106
+
107
+ ---
108
+
109
+ ## Output Expectations
110
+
111
+ This is a knowledge/API skill — it produces API calls and their interpreted results, plus SOQL against `ArchiveActivity`. It does not generate deployable metadata. Deliverables per task: the correct operation invocation(s), the right success-signal branching, and a verify-after-write confirmation.
112
+
113
+ ---
114
+
115
+ ## Reference File Index
116
+
117
+ | File | When to read |
118
+ |------|-------------|
119
+ | `references/connect-api-operations.md` | Before constructing any Archive Connect API call — full per-operation contracts, success signals, and limits |
120
+ | `references/archive-activity-entity.md` | For any job-status / failure / progress / log task — `ArchiveActivity` field reference and its link to the log-download endpoints |
121
+ | `examples/monitor-failed-jobs.md` | To follow an end-to-end monitoring flow: find failed/in-progress jobs, then download their logs |
@@ -0,0 +1,47 @@
1
+ # Example — Monitor failed archive jobs and download their logs
2
+
3
+ End-to-end flow for the most common archive-admin task: find the jobs that failed or stalled, read why, and pull the logs that show which records didn't make it.
4
+
5
+ ## 1. Find the jobs (query ArchiveActivity)
6
+
7
+ `ArchiveActivity` is not Flow-queryable, so use SOQL/Connect:
8
+
9
+ ```sql
10
+ SELECT Id, Name, Status, Type, StartTime, ProgressPercentage,
11
+ TotalRecordCount, SucceededCount, FailedCount, SkippedRootRecordsCount,
12
+ RootEntityName, FailureReason
13
+ FROM ArchiveActivity
14
+ WHERE (Status = 'Failed' OR Status = 'In Progress' OR Status = 'Ended With Errors')
15
+ AND StartTime >= LAST_N_DAYS:7
16
+ ORDER BY StartTime DESC
17
+ ```
18
+
19
+ Read each row's `ProgressPercentage`, `SucceededCount`/`FailedCount`, `RootEntityName`, and `FailureReason` to summarize health. In-progress jobs have a blank `EndTime`.
20
+
21
+ ## 2. For each failed job, get the logs
22
+
23
+ Take the job's `Id` (`8qv…`) as `requestId` and its `Type` as `reportType`. `{activity.Type}` below is a placeholder for that job's own `Type` field value (e.g. `Archive`, `Purge`, `Unarchive`, `Analyzer`) — substitute the actual value per job; a hardcoded/mismatched `reportType` returns no log.
24
+
25
+ **Execution-detail log:**
26
+ ```
27
+ GET /platform/data-resilience/archive/log/execution-details-stream-url
28
+ ?requestId={activity.Id}&reportType={activity.Type}
29
+ ```
30
+
31
+ **Failed-records log:**
32
+ ```
33
+ GET /platform/data-resilience/archive/log/failed-records-stream-url
34
+ ?requestId={activity.Id}&reportType={activity.Type}
35
+ ```
36
+
37
+ Each returns `{ url }`. **Check `url != null`** before using it — a `null` url means no log was resolved for that job/type (e.g. the job produced no failed-records file), not an error to retry blindly.
38
+
39
+ ## 3. Summarize
40
+
41
+ Per failed/in-progress job, report: Name, Status, ProgressPercentage, SucceededCount, FailedCount, RootEntityName (target object), FailureReason, and the two log URLs (or "no log available" when `url` is null).
42
+
43
+ ## Pitfalls this flow avoids
44
+
45
+ - **Not** attempting a Flow over `ArchiveActivity` (`isProcessEnabled=false` → "You can't get ArchiveActivity records in a flow").
46
+ - Passing the activity's real `Type` as `reportType` (a mismatch returns no log).
47
+ - Treating a 201 with `url: null` as a usable log.
@@ -0,0 +1,59 @@
1
+ # ArchiveActivity — Data Model Reference
2
+
3
+ `ArchiveActivity` is the platform entity that records **job metadata for Salesforce Archive jobs** (archive, purge, analyze, unarchive, export). Every archive job produces one `ArchiveActivity` record; you read it to learn a job's status, progress, and outcome, and you use its `Id` + `Type` to download the job's logs.
4
+
5
+ ## Entity facts
6
+
7
+ | Property | Value |
8
+ |----------|-------|
9
+ | API name | `ArchiveActivity` |
10
+ | Key prefix | `8qv` (record Ids start `8qv…`) |
11
+ | Owning product | Salesforce Archive (Trusted Services Archive) |
12
+ | Kind | **Standard platform entity** (not a custom object) |
13
+ | License gate | Requires the Trusted Services Archive add-on (`TrustedServicesArchive.hasTrustedServicesArchive`) — absent on orgs without the add-on |
14
+ | Name field | Auto-number, mask `ARCV-{0000000000}` |
15
+ | Flow-enabled? | **No** — `isProcessEnabled=false`. A Flow "Get Records" element on it fails: "You can't get ArchiveActivity records in a flow." Query via SOQL / Connect / Reports. |
16
+
17
+ ## Fields
18
+
19
+ | Field | Type | Meaning |
20
+ |-------|------|---------|
21
+ | `Name` | Auto-number | `ARCV-…` job identifier |
22
+ | `Status` | enum `ArchiveActivityStatus` | Lifecycle: scheduled, running / **In Progress**, completed, **Failed**, canceled, **Ended With Errors** |
23
+ | `Type` | enum `ArchiveActivityType` | The job mode: Archive, Purge, Analyze(r), Unarchive, Export-to-external-bucket, Export-and-download. **This is the `reportType` for log downloads.** |
24
+ | `StartTime` | DateTime | When execution began |
25
+ | `EndTime` | DateTime | When execution finished/terminated (blank while running) |
26
+ | `ArchivePolicyDefinition` | Lookup (FK) | Parent `ArchivePolicyDefinition`; child relationship `ArchiveActivities` |
27
+ | `RootEntityName` | Text | API name of the target sObject being archived |
28
+ | `TotalRecordCount` | Long | Records initially selected (succeeded + failed + skipped) |
29
+ | `AttemptedRootRecordsCount` | Long (formula) | `SkippedRootRecordsCount + FailedCount + SucceededCount` — top-level records actually attempted |
30
+ | `SkippedRootRecordsCount` | Long | Top-level records skipped (validation, exclusion filters, data-protection thresholds) |
31
+ | `SucceededCount` | Long | Records processed without error |
32
+ | `FailedCount` | Long | Records that failed (validation, missing refs, exceptions) |
33
+ | `ProgressPercentage` | Double | Percent complete |
34
+ | `FailureReason` | Long text | Why the job failed/partially completed (system error messages or policy-level failures) |
35
+ | `RecordsSizeInMb` | Text | Estimated total size of processed records (MB) |
36
+
37
+ > The entity also defines a `ProgressIcon` display formula for list views; it is not useful for programmatic monitoring — read `Status`, `ProgressPercentage`, and the count fields directly.
38
+
39
+ ## How ArchiveActivity links to the Connect API
40
+
41
+ The log-download endpoints (`get-execution-details-stream-url`, `get-failed-records-stream-url`) take:
42
+
43
+ - `requestId` = an `ArchiveActivity` **`Id`** (`8qv…`) of a completed, log-producing job, and
44
+ - `reportType` = that same activity's **`Type`** value.
45
+
46
+ So the monitoring pattern is: **query `ArchiveActivity` → pick the job(s) of interest → pass each job's `Id` and `Type` to the stream-url operation → check `url != null`.** See `../examples/monitor-failed-jobs.md`.
47
+
48
+ ## Example SOQL
49
+
50
+ ```sql
51
+ -- Failed or in-progress jobs in the last 7 days, newest first
52
+ SELECT Id, Name, Status, Type, StartTime, EndTime, ProgressPercentage,
53
+ TotalRecordCount, SucceededCount, FailedCount, SkippedRootRecordsCount,
54
+ RootEntityName, FailureReason, ArchivePolicyDefinitionId
55
+ FROM ArchiveActivity
56
+ WHERE (Status = 'Failed' OR Status = 'In Progress' OR Status = 'Ended With Errors')
57
+ AND StartTime >= LAST_N_DAYS:7
58
+ ORDER BY StartTime DESC
59
+ ```
@@ -0,0 +1,157 @@
1
+ # Salesforce Archive Connect API — Operations Reference
2
+
3
+ All operations are under the base path `/platform/data-resilience/archive/`. Contracts verified live against archived data. Read the operation you need before constructing the call — several contracts are non-obvious.
4
+
5
+ ## Operations Summary
6
+
7
+ | Operation | Purpose | Method + Path | Verify with |
8
+ |-----------|---------|---------------|-------------|
9
+ | `search-archived-records` | read | `POST /search` | — |
10
+ | `search-archived-records-with-sharing-rules` | read (Agentforce) | `POST /search/with-sharing-rules` | — |
11
+ | `get-search-archived-records-next-page` | read | `GET /search/next/{scrollId}` | — |
12
+ | `run-analyzer` | write | `POST /analyzer/run` | `get-analyzer-report` |
13
+ | `get-analyzer-report` | read | `GET /analyzer/report` | — |
14
+ | `unarchive-records` | write | `POST /unarchive` | re-run `search-archived-records` |
15
+ | `forget-archived-records` (RTBF) | write | `POST /rtbf` | `get-rtbf-status` |
16
+ | `get-rtbf-status` | read | `GET /rtbf/{requestId}` | — |
17
+ | `mask-archived-records` | write | `POST /mask` | `get-masking-status` |
18
+ | `get-masking-status` | read | `GET /mask/{requestId}` | — |
19
+ | `get-execution-details-stream-url` | read | `GET /log/execution-details-stream-url` | — |
20
+ | `get-failed-records-stream-url` | read | `GET /log/failed-records-stream-url` | — |
21
+ | `get-archive-storage-used` | read | `GET /storage/archive-used` | — |
22
+
23
+ **Deprecated — do not use** (no successor; currently 500): `global-search-by-id`, `get-global-search-results`, `view-archived-records`. Use `search-archived-records` (+ `get-search-archived-records-next-page`) for all archived-record lookups.
24
+
25
+ ### Verify-after-write dependencies
26
+
27
+ ```mermaid
28
+ graph TD
29
+ run_analyzer["run-analyzer"] -.verify.-> get_analyzer_report["get-analyzer-report"]
30
+ forget_archived_records["forget-archived-records"] -.verify.-> get_rtbf_status["get-rtbf-status"]
31
+ mask_archived_records["mask-archived-records"] -.verify.-> get_masking_status["get-masking-status"]
32
+ unarchive_records["unarchive-records"] -.verify.-> search_archived_records["search-archived-records"]
33
+ ```
34
+
35
+ ---
36
+
37
+ ## search-archived-records (`POST /search`)
38
+
39
+ Search archived records by object, filters, date ranges, and sort.
40
+
41
+ **Required**: `sobjectName` + at least 1 filter. Missing them returns a clean envelope validation error (`statusCode 400`).
42
+
43
+ **Inputs**:
44
+ - `sobjectName` *(string)* — API name of the sObject to search.
45
+ - `filters` *(array)* — Filter conditions, each `{field, value}` where **both are required strings**. `value` is a single string — **not** an array, **not** nullable (null/omitted → `400 "This field may not be null"`). There is **no `operator` field**. **At least 1, up to 6 filters, combined with AND only** (OR is not supported). Example: `[{"field":"Subject","value":"Foo"}]`.
46
+ - `dateRanges` *(array)* — Primary date filter: array of `{field, from, to}`. `from`/`to` must be full ISO-8601 datetime (`"2020-01-01T00:00:00Z"`); date-only → `400 JSON_PARSER_ERROR` (xsd:dateTime). Use the special field `archive_date` to filter by archive date instead of `CreatedDate`/`ModifiedDate`.
47
+ - `dateRange` *(object)* — Optional **singular** convenience range `{field, from, to}`; the controller folds it into `dateRanges` (one-element equivalent). Same singular shape that `unarchive-records` uses.
48
+ - `fields` *(array)* — Field API names to return.
49
+ - `pageSize` *(integer)* — Records per page; default 25, max 1000.
50
+ - `sortDirection` *(string)* — `asc`/`desc`, case-insensitive, default `asc`; an invalid value → 400.
51
+
52
+ **Output**: HTTP **201**, `body = { records[], total_result_count, scroll_id }`, `body.statusCode = 200`, `errorMessage` null on success. **Branch on `body.statusCode`, not the HTTP code** — once past framework validation the response is always 200/201 even on logical failure, with the error in `errorMessage` + `statusCode`.
53
+
54
+ **Excluded objects**: `Feed`, `History`, `Relation`, `Share` are not searchable; Files/Attachments are not retrievable.
55
+
56
+ ### Pagination
57
+
58
+ Read records inline from each response. If `body.scroll_id != "-1"`, call `get-search-archived-records-next-page` with that `scroll_id`. **STOP when `scroll_id == "-1"`** — calling next-page with `"-1"` (the terminal sentinel) → 500. There is no separate fetch-by-requestId step.
59
+
60
+ ## get-search-archived-records-next-page (`GET /search/next/{scrollId}`)
61
+
62
+ **Input**: `scrollId` *(string, path param)* — the `body.scroll_id` from a prior search. Stop when it is `"-1"`; never call with `"-1"`.
63
+ **Output**: `body` (next page), `errorMessage`, `statusCode` — same envelope as search.
64
+
65
+ ---
66
+
67
+ ## search-archived-records-with-sharing-rules (`POST /search/with-sharing-rules`)
68
+
69
+ Archive search **optimized for Agentforce agents**. (There is no `/search/related` endpoint — this is the operation that takes the JSON filter map.) Gated by the **`ViewArchivedRecords`** user permission (unlike plain `search-archived-records`, which uses `ViewSearchPage`).
70
+
71
+ **Required**: `objectName`, `filtersJson`.
72
+
73
+ **Inputs**:
74
+ - `objectName` *(string)* — API name of the sObject.
75
+ - `filtersJson` *(string)* — JSON-encoded **OBJECT MAP** of `fieldName → value`, e.g. `"{\"Subject\":\"Foo\",\"Status\":\"New\"}"`. **NOT** an array of `{field,value}` objects — the array form is rejected with `isSuccess:false "No valid filters provided. Please provide filtersJson"`.
76
+ - `dateField` *(string)* — API name of the date field for temporal filtering.
77
+ - `startDate` / `endDate` *(string)* — search window bounds (ISO-8601).
78
+ - `maxResults` *(integer)* — max records to return; default 100.
79
+
80
+ **Output**: `isSuccess` *(boolean — **branch on THIS, not the HTTP status**)*, `totalResultCount`, `records` (rich-text summary of ~5 key fields per record), `recordsJson` (HTML-entity-encoded JSON string), `errorMessage`, `message`, `warnings` (schema-validation/auto-fix advisories). **Validation failures surface as HTTP 201 + `isSuccess:false` + `errorMessage`, never a 4xx.** Pattern: `isSuccess:false` + `errorMessage` → caller bug; `isSuccess:true` + `recordsJson` → success.
81
+
82
+ ---
83
+
84
+ ## run-analyzer (`POST /analyzer/run`) + get-analyzer-report (`GET /analyzer/report`)
85
+
86
+ `run-analyzer` triggers the analyzer; HTTP 201, output `message` *(string, human-readable status)*. **`isRunning` is ALWAYS `null`** — the controller only populates `message`; never branch on `isRunning`. Poll `get-analyzer-report` to confirm completion. Non-destructive / idempotent.
87
+
88
+ `get-analyzer-report` returns `body` with: `topRecords[]` (`{objectName, objectLabel, objectIcon, size, count, usagePercent}` per archivable sObject), `topFiles[]` (parallel shape for files), `fileGeneralStorage`/`dataGeneralStorage` (`{storageUsed, storageRemaining, usagePercent}`), and `createdDateReport` (`"DD/MM/YYYY HH:MM:SS"`).
89
+
90
+ ---
91
+
92
+ ## unarchive-records (`POST /unarchive`)
93
+
94
+ Restore archived records back into live storage by criteria.
95
+
96
+ **Inputs**:
97
+ - `sobjectName` *(string)* — sObject whose records to unarchive.
98
+ - `filters` *(array)* — criteria identifying which archived records to restore.
99
+ - `dateRange` *(object)* — optional **SINGULAR** range `{field, from, to}` (reads `getDateRange()`, unlike `/search` which uses plural `dateRanges`); full ISO-8601 datetimes. Omit to unarchive by filters alone.
100
+
101
+ **Caps**: ≤1000 matched records (else not processed); ≤50 unarchive requests/hour/org. Restores the **whole archived hierarchy** of each match. Requires the **`UnarchiveSdk`** user permission (on top of org-level Archive enablement).
102
+
103
+ **Output**: `body` (unarchive job details incl. job id), `errorMessage`, `statusCode`. **Verify** by re-running `search-archived-records`. **Rollback**: re-archive via a new archive job with the same criteria.
104
+
105
+ ---
106
+
107
+ ## forget-archived-records / RTBF (`POST /rtbf`) + get-rtbf-status (`GET /rtbf/{requestId}`)
108
+
109
+ Submits a Right-To-Be-Forgotten erasure request.
110
+
111
+ **Input**: `criteria` *(array of `{sobject, field, value}`)* — ≤10 items, **one per object type**; ≤10,000 root records erased per org/day; field/object names case-insensitive. Deletes the **entire archived hierarchy** of each match (no partial deletion). Note: the criterion must match a record archived **as a root** — filtering a parent (e.g. Account by Id) when only its children were archived matches nothing.
112
+
113
+ **Output**: HTTP 201, `body.request_id` *(UUID)*. Poll `get-rtbf-status` (path param `requestId` = that UUID) → `body.status` (e.g. `"Request is open. Scan is still in progress"`). **Rollback**: none — RTBF erasure is permanent.
114
+
115
+ ---
116
+
117
+ ## mask-archived-records (`POST /mask`) + get-masking-status (`GET /mask/{requestId}`)
118
+
119
+ Submits a PII-masking (anonymization) request — irreversibly replaces detected PII values with placeholders (e.g. `redacted@example.com`) while keeping the record + non-PII fields searchable.
120
+
121
+ **Input**: `criteria` *(array of `{sobject, field, value}`)* — same shape as RTBF.
122
+
123
+ **Behavior**: permanent; one-time per record (re-requests on an already-masked record are ignored); shares the 10,000/day RTBF rate limit; **PII fields are auto-detected** (you cannot choose them); records under legal hold / retention lock are excluded; cascades to child records. Available **only via this API** (not in the Archive UI).
124
+
125
+ **Permission**: gated by the **`Rtbf`** user permission — the SAME permission as RTBF (`maskArchivedRecords` runs the RTBF access check), not a separate masking entitlement.
126
+
127
+ **Output**: HTTP 201, `body.request_id` *(UUID)*. Poll `get-masking-status` (path param `requestId`); status reaches **HANDLED** when complete. **Rollback**: none — anonymization is permanent.
128
+
129
+ ---
130
+
131
+ ## Log Downloads — get-execution-details-stream-url / get-failed-records-stream-url
132
+
133
+ Both are `GET /log/...` and mint a one-time presigned download URL. `get-execution-details-stream-url` → the execution-detail log; `get-failed-records-stream-url` → the failed-records log (records that did not process). Identical contract.
134
+
135
+ **Output**: `{ url }`. **`url != null` is success; `url: null` means no log was resolved** (missing/incorrect `requestId`/`reportType`, or the activity produced no such log) — always check `url != null`; never treat `url:null` or the 201 status alone as success.
136
+
137
+ **Required inputs**:
138
+ - `requestId` *(string)* — the **`ArchiveActivity` Id** (`8qv…` key) of a completed, log-producing job present in the archiver backend — **not** a search requestId. A missing/non-matching id → `url:null`.
139
+ - `reportType` *(string)* — that activity's `Type`: `Archive | Unarchive | Analyzer | Purge | Export-to-external-bucket | Export-and-download`. Omitting it → `url:null`.
140
+ - `sobjectName` *(string, optional)* — the backend self-resolves it.
141
+
142
+ This is the bridge between `ArchiveActivity` (see `archive-activity-entity.md`) and downloadable logs.
143
+
144
+ ---
145
+
146
+ ## get-archive-storage-used (`GET /storage/archive-used`)
147
+
148
+ Returns `body.usedStorage[4]` (doubles — per-tier bytes consumed) and `body.availableStorage[4]` (per-tier capacity) — two **parallel positional arrays** (NOT a flat metric, NOT key/value maps). The 4 slots are the same in both:
149
+
150
+ | Index | Meaning |
151
+ |-------|---------|
152
+ | 0 | Salesforce org **DATA** storage |
153
+ | 1 | Salesforce org **FILE** storage |
154
+ | 2 | Archive-tier **RECORDS** storage |
155
+ | 3 | Archive-tier **FILE** storage |
156
+
157
+ **`availableStorage[2]` and `[3]` (the archive tier) are ALWAYS 0**: the controller hardcodes them (`ARCHIVER_AVAILABLE_STORAGE = 0`) because archive storage is unmetered, so a `0` there means "not tracked", NOT "no space left". Only `availableStorage[0]`/`[1]` (live org data/file remaining) are real. All values rounded to 2 decimals.