Send on-demand sync audits to S3, SQS, or SNS¶
Overview¶
When a PolicySync connector is configured with on-demand resource sync, it can publish one ON_DEMAND audit event per requested resource to the policysync_audit stream. Each event carries that resource's load outcome (found, added, updated, deleted, or not-found), its outcome codes, the policies applied during the sync, and the request context.
These events flow through the same Audit Server path as every other connector audit. Once you turn the feature on for the connector, the ON_DEMAND events reach whichever destinations you have already configured for the policysync_audit stream — Apache Solr, Amazon S3 (through Audit Fluentd), Amazon SQS, and Amazon SNS (through an SNS→SQS subscription).
Because the stream is indexed into Apache Solr, the events are also visible in Privacera Portal alongside your other audit records.
The feature is opt-in and controlled by a single connector setting. When it is off, on-demand syncs still run and apply grants exactly as before — only the per-resource ON_DEMAND audit is not produced.
There are two parts to this configuration:
- Point the
policysync_auditstream at S3, SQS, or SNS using the standard destination configuration (Choose your destinations). - Enable the
ON_DEMANDaudit on the connector so the events are produced (Setup).
Prerequisites¶
On-demand resource sync is configured and working on the connector, so the connector is polling the Ops Server for RESOURCE_SYNC tasks.
Choose your destinations¶
Choose where the audits go before you turn the feature on, so the events have somewhere to land as soon as the connector starts producing them.
ON_DEMAND events travel on the policysync_audit stream, so they follow the destinations you set for that stream. There is no separate on-demand destination list — configure the outputs once, and the on-demand events are included automatically.
| Destination | How to configure |
|---|---|
| Amazon S3 | Route the stream through Audit Fluentd to S3. See Send Audits to S3, then optionally Query PolicySync Audits in Amazon Athena. |
| Amazon SQS | Enable the sqs destination and set the queue URL and region. See Send Audits to Amazon SQS. |
| Amazon SNS | Deliver to SQS from an SNS topic by subscribing your SQS queue to the topic, then configure the sqs destination as above. |
You also need AUDITSERVER_POLICYSYNC_AUDIT_HANDLER_V2_ENABLED: true. See Connector Audit Output Destinations.
Note
Make sure the policysync_audit destination list (AUDITSERVER_POLICYSYNC_AUDIT_DESTINATIONS_NAMES) includes the destinations you want. Privacera Manager does not add sqs for you — see Connector Audit Output Destinations.
Setup¶
Follow these steps on the host where Privacera Manager is installed.
- Sign in to that host.
- Open the Manager configuration directory:
Bash - Copy the connector sample variables into
custom-varsif you have not already. The-nflag avoids overwriting an existing file. Replace the file name with the one for your connector (for example,vars.connector.databricks.unity.catalog.yml).Bash - Edit the connector custom variables file:
Bash -
Enable the on-demand audit by adding (or updating) the following property. This renders the connector property
policysync.on-demand.audit.enable=true.Property Purpose Privacera Manager default CONNECTOR_ON_DEMAND_AUDIT_ENABLEEmit one ON_DEMANDaudit event per requested resource, on thepolicysync_auditstreamfalseLeaving this
falseonly suppresses the audit. Resource load, grant application, and the Ops Server task-status flow behave the same either way.
| YAML | |
|---|---|
- Confirm on-demand processing is enabled for the connector (
CONNECTOR_ON_DEMAND_PROCESSING_ENABLE: "true"); the audit is only produced when on-demand syncs actually run. - Apply the configuration with Privacera Manager, then restart the connector.
Event format¶
ON_DEMAND is one more eventType on the policysync_audit stream, so it uses the same wire contract as every other PolicySync connector event: one JSON object per event, UTF-8, camelCase property names, and UPPERCASE enum-like string values. Unknown root or extraInfo keys may appear in future releases — ignore or store them unless you explicitly handle them. See Policy Tracing and Explainability for the shared contract and the other event types.
The same payload shape is indexed into Apache Solr and forwarded to Amazon S3, Amazon SQS, and Amazon SNS.
Envelope fields¶
An ON_DEMAND event fixes several envelope fields to constant values, because it is always produced by the resource loader running from an on-demand task.
| Field | Type | Description |
|---|---|---|
eventType | String | Always ON_DEMAND. Filter on this field to select on-demand syncs |
evtTime | String | When the event was produced. ISO 8601, for example 2026-07-28T07:14:35Z |
timeTakenMs | Integer | How long the sync took, in milliseconds. Every event from the same sync carries the same value |
status | String | COMPLETED or FAILED for this one resource |
msg | String | Readable outcome for this resource, taken from the outcome code |
connectorType | String | Connector family, for example databricks_unity_catalog |
connector | String | Connector instance name |
changeLogId | Integer | Identifies the sync run. Every event from the same sync carries the same value |
changeLogType | String | Always SERVICE_RESOURCES |
loaderType | String | Always LOAD_SERVICE_RESOURCES |
triggerFrom | String | Always EVENT, meaning an Ops Server request triggered the sync rather than the schedule |
ondemandTaskId | Integer | Identifies the on-demand request. Use it to group every resource event from one request |
uuid | String | Document identifier assigned by the destination. May be null |
extraInfo | Object | The on-demand payload. Holds the outcome codes, what happened, and what was requested |
A few of these fields need more than a one-line description:
eventType— a scheduled resource load reportsLOADERinstead, so filtering onON_DEMANDselects only the syncs that were explicitly requested.status— the outcome for this one resource, so one request can produce bothCOMPLETEDandFAILEDevents. The exception is a failed ACL operation, which counts for the whole task: every resource in the request that reached grant application is thenFAILED, even if its own grants applied. See Outcome codes.msg— fixed text for the outcome, safe to show to a user. It never contains the target service's error text, which is inerrorDetails.serviceError. The wording can change between releases, so code should readextraInfo.response[]instead.timeTakenMs— measured from the start of the resource load to the moment the event is produced.
Output: what the sync did¶
The extraInfo.output object describes what happened to this resource. The outcome codes are next to it, in extraInfo.response; see Outcome codes.
| Field | Type | Description |
|---|---|---|
resources | Object | The change counts for this resource, keyed by its type — table, schema, catalog, and so on |
policies | Array | The policies applied during this sync, each with its ACL counts |
policiesTruncated | String | Optional. Reads kept/total, for example 40/112, when the policy list was shortened to fit the audit size limit |
aclsExecuted | Object | Optional. ACL totals of the policies applied in this sync |
errorDetails | Object | Optional. Why the outcome failed |
When a field is empty rather than absent:
resourcesis{}when the resource was not found, when the load failed, or when the request could not be matched to a single resource.policiesis[]when this resource did not change, because no grants were applied to it.aclsExecutedis absent when the resource did not change. It is present on successful events too, not only on failures.errorDetailsis absent on a success, because there is nothing to report.
Resource counts¶
The value under output.resources.<type>. Each count is 0 or 1, because one event describes exactly one requested resource.
| Field | Type | Description |
|---|---|---|
found | Integer | 1 when the resource exists in the source. A deleted resource is no longer there, so it has found 0 and deleted 1 |
added | Integer | 1 when the resource was newly added to the connector store |
updated | Integer | 1 when an existing resource was updated |
deleted | Integer | 1 when the resource was removed from the source and its grants were revoked |
The policies that were applied¶
Each entry of output.policies:
| Field | Type | Description |
|---|---|---|
id | String | Policy identifier |
version | String | Policy version |
name | String | Policy name |
label | String | Correlation or trace label; empty when the policy carries none |
aclsExecuted | Object | ACL counts for this policy: total, applied, failed, skipped, and timeTakenMS |
failedReasons | Object | The target service's error text, mapped to how many ACL operations failed with it. {} when nothing failed. Not normalized: the text is as the connector wrote it and can include table and principal names, so treat it as sensitive. AWS Lake Formation normalizes its own but keeps single-word names |
skippedReasons | Object | Each skip reason, mapped to how many ACL operations were skipped for it. {} when nothing was skipped |
ownerIgnore | Object | Optional. How many ACL operations were skipped because the principal owns the resource |
ACL totals¶
The output.aclsExecuted object adds up the ACL operations of the policies applied in this on-demand sync. Read the counts here, whatever the outcome.
Note
The totals, like response[], cover the whole on-demand sync, not only this resource. When one sync covers several resources, each changed resource's event reports the same totals.
| Field | Type | Description |
|---|---|---|
total | Integer | ACL operations attempted in this sync |
applied | Integer | ACL operations that succeeded |
failed | Integer | ACL operations that failed. Any value above 0 makes this event FAILED, even when the sync completed overall |
skipped | Integer | ACL operations that were skipped rather than attempted |
Absent when the resource did not change, because no grants ran for it.
Truncated policy lists
These totals are computed across all policies in the sync, before any truncation. When policiesTruncated is present, aclsExecuted can therefore describe more policies than policies lists. That is deliberate: the counts stay accurate even when the list is shortened, so a failure inside a truncated-away policy is still visible.
Why the outcome failed¶
The output.errorDetails object explains a failure. It never appears on a successful outcome, and it does not appear on every failed one either — a resource that never reached grant application has no cause to report. When it is present, it carries only the parts that apply.
| Field | Type | Description |
|---|---|---|
topReasons | Array | Optional. The most common failure reasons in this sync, as {reason, count} objects. Present only when grants were attempted. Not normalized, like policies[].failedReasons: the text can include table and principal names |
serviceError | String | Optional. The error from the target service, or from the sync, normalized as described below and at most 512 characters long |
Which of the two appear depends on the outcome; see Outcome codes. topReasons is a summary, and policies[].failedReasons has the per-policy breakdown. ACL counts are in ACL totals, and the failure's codes in extraInfo.response[].
In serviceError, ARNs, email addresses, IPv4 addresses, request IDs, and quoted or dotted object names are replaced with ?. A name with none of these shapes, such as a single-word table name, is kept, so treat the text as diagnostic data.
Input: the original request¶
The extraInfo.input object echoes back the request this event answers, so you can match an event to its request without calling the Ops Server.
| Field | Type | Description |
|---|---|---|
createTime | Integer | When the request was created, in epoch milliseconds |
syncType | String | The request type. RESOURCE_SYNC for on-demand resource syncs |
source | String | Which Ops Server source the request arrived through, for example REST_API or MSK |
appType | String | The type of application that made the request, for example PS_CONNECTOR |
resources | Array | Holds one entry: the resource this event describes |
The source value is the OPS_BRIDGE_SOURCE name configured for the connector, so it names the path the request came in through — the REST API, MSK, or another configured source. See On-Demand Sync for PolicySync Connectors.
Each element of input.resources[]:
| Field | Type | Description |
|---|---|---|
type | String | The requested resource type, such as table, schema, or catalog |
values | Object | The resource as it was requested, such as catalog, schema, and table, plus the requestor fields below |
Inside input.resources[].values:
| Field | Type | Description |
|---|---|---|
requestor_id | String | Who requested this resource. Present when the request supplied it |
requestor_create_time | String | When this resource was requested, in epoch seconds |
These two fields are per resource, not per request. If one request asks for several resources on behalf of different people, each event carries its own requestor.
Sample event¶
One resource (table1) added by on-demand task 6, with one policy applied:
All three ACL operations applied, so response has one entry, 2001 SYNCED_GRANTS_APPLIED, with count 3.
A request for three resources produces three events like this one, all sharing ondemandTaskId, changeLogId, and timeTakenMs.
Outcome codes¶
Every requested resource gets one event. Its status and msg give the resource's outcome, and extraInfo.response[] lists its outcome codes, on success and on failure, in the same shape as on ACCESS and POLICY_RESPONSE events. Branch on response[].code, not on msg: the codes stay the same between releases, the wording may not. For the fields of an entry, see The response entry.
Where each one shows up
Privacera Portal shows msg in the Message column of the audit list, for operators. response[] is in the event payload, not a Portal column, for consumers reading the events from Solr, S3, Amazon SQS, or Amazon SNS. Both describe the same outcome.
| Outcome | status | msg | When it happens | errorDetails |
|---|---|---|---|---|
SYNCED_GRANTS_APPLIED | COMPLETED | Resource synced; access grants applied | The resource changed and a matching policy applied its grants | — |
NO_POLICY_FOUND | COMPLETED | Resource synced; no matching policy found, so no grants were applied | The resource changed, but no policy matched it, so there was nothing to grant | — |
NO_CHANGE_DETECTED | COMPLETED | No change detected since last sync, grant not reapplied | The resource was loaded and is unchanged since the last sync, so grants were not re-applied | — |
RESOURCE_DELETED | COMPLETED | Resource deleted from source | The resource no longer exists in the source and was removed; its grants were revoked | — |
RESOURCE_NOT_FOUND | FAILED | Resource not found in source; nothing to apply | The requested resource does not exist in the source, so nothing was synced and no grants were applied | none — it never entered grant application, so the sync's grant error is not its cause |
RESOURCE_LOAD_FAILED | FAILED | Resource load failed | The resource load itself did not complete (for example the source could not be reached, or the resource key could not be persisted) | serviceError |
GRANT_FAILED | FAILED | Failed to grant | Grant application ran for this resource and all ACL operations failed, or grant application failed without a failed ACL operation | topReasons, serviceError |
GRANT_PARTIALLY_APPLIED | FAILED | Grants partially applied; some grants failed | Grant application ran and some ACL operations succeeded while others failed | topReasons, serviceError |
What response[] lists¶
- A resource that reached grant application (it changed, and the on-demand task ran ACL operations) lists the outcomes of the task's ACL operations, grouped, with a
counteach. For example,2001for an applied grant,1004for a grant skipped because the grantee owns the resource, or5001for a transient failure. The codes are inON_DEMANDcodes and template-based row filter codes; see also the limitation on which ACL operations are counted.- These entries cover the whole task, not this resource alone. Every changed resource in the request lists the same ones, so do not add up
countacross a request's events. - A resource whose outcome is
RESOURCE_DELETEDalso lists2004,count1, for example[{2001, count 3}, {2004, count 1}]. If the load, grant application, or any ACL operation of the task failed, its outcome is that failure and it lists no2004. To detect a deletion in every case, readoutput.resources.<type>.deleted.
- These entries cover the whole task, not this resource alone. Every changed resource in the request lists the same ones, so do not add up
- Any other resource lists its own outcome, one entry with
count1, from the table below. - When the resource load fails, every resource in the request also lists the load failure's code:
4505RESOURCE_LOAD_FAILED;5001SYSTEM_ERRORwhen it is transient;1006RESOURCE_NOT_FOUNDor4503PRINCIPAL_NOT_FOUNDwhen the backend reports a missing resource or principal. - A
FAILEDevent never lists onlySUCCESSentries.
The resource-level codes:
code | strCode | Category | Meaning |
|---|---|---|---|
1006 | RESOURCE_NOT_FOUND | EXPECTED | The resource does not exist in the source, for example a dropped table, so nothing was applied. The event status is FAILED |
2001 | SYNCED_GRANTS_APPLIED | SUCCESS | The resource changed and a matching policy applied its grants |
2002 | NO_CHANGE_DETECTED | SUCCESS | The resource is unchanged since the last sync, so grants were not re-applied |
2003 | NO_POLICY_FOUND | SUCCESS | The resource changed, but no policy matched it, so no grants were applied |
2004 | RESOURCE_DELETED | SUCCESS | The resource no longer exists in the source; it was removed and its grants were revoked |
4502 | GRANT_FAILED | PERMANENT | The resource changed, but grant application failed with no failed ACL operation, for example before any ACL operation ran or because the changelog failed after the grants applied. When grant application could not start and the failure was classified, the entry is that classification instead, such as 5001 |
4505 | RESOURCE_LOAD_FAILED | PERMANENT | The resource load did not complete, for a reason that is neither transient nor a not-found error, or that could not be classified. Reported for every resource in the request |
5001 | SYSTEM_ERROR | TRANSIENT | The resource load, or the start of grant application, failed for an infrastructure reason, such as throttling or a dropped connection. A load interrupted by a connector restart is also reported this way, with origin PRIVACERA, on every resource in the request. A new request may succeed |
Which resources a failure affects¶
| Outcome | Scope within the request |
|---|---|
RESOURCE_NOT_FOUND | Only the missing resource. A load failure the backend reports as not found is RESOURCE_LOAD_FAILED instead, and lists 1006 on every resource in the request |
RESOURCE_LOAD_FAILED | Every resource in the request, because the load did not finish |
GRANT_FAILED, GRANT_PARTIALLY_APPLIED | Only resources that changed, since only those reached grant application; all of them when any ACL operation of the task failed |
Partial grants are detected from the counts
GRANT_PARTIALLY_APPLIED is decided from output.aclsExecuted, not from the changelog status. Individual ACL operations can fail while the sync completes overall, so a resource where 4 of 6 grants applied is reported as a partial failure rather than a clean success.
Not found is not the same as deleted
- A deleted resource existed, was removed from the source, and had its grants revoked. That is successful work, so
RESOURCE_DELETEDisCOMPLETED. - A not-found resource never existed as requested, so nothing was synced.
RESOURCE_NOT_FOUNDisFAILEDso that the request is flagged. Its entry is1006, categoryEXPECTED: there is nothing to retry.
Requests that cannot be matched to one resource
A coarse or partial request — for example a parent path whose children are keyed individually — cannot always be matched back to a single resource's load result. Such an entry reports NO_CHANGE_DETECTED with empty resources and policies. The sync itself still ran; only the per-resource attribution is unavailable. Two consequences worth knowing when reading these events: a coarse re-sync whose child resources did change still reports "no change" for the parent entry, and such an entry stays COMPLETED even when grant application failed elsewhere in the same sync.
Failed use-cases¶
A failed outcome always emits status = FAILED, a readable msg from the table above, its codes in extraInfo.response[], and — where there is a cause to report — the underlying error in errorDetails.serviceError.
msg never carries the raw error
msg comes only from the outcome code, so it is safe to display. The error text is in other fields; read them for diagnosis:
errorDetails.serviceError: the target service's error, with ARNs, request IDs, and similar identifiers replaced. See Why the outcome failed.errorDetails.topReasonsandpolicies[].failedReasons: each ACL operation's failure text as the connector wrote it, which can include table and principal names. AWS Lake Formation normalizes its own likeserviceError, but keeps single-word names.output.lfForward.reason: the raw cause.
Treat these fields as sensitive, for every connector, when you route ON_DEMAND events to Amazon SQS, Amazon SNS, or Amazon S3. The same text also reaches POLICY_RESPONSE and ACCESS events; see POLICY_RESPONSE.
Resource not found¶
output.resources and output.policies are empty, and there is no errorDetails: the resource never reached grant application. input still shows which resource was requested. response[] has one entry, 1006 RESOURCE_NOT_FOUND, category EXPECTED.
Resource load failed¶
The load did not complete, so every resource in the task is FAILED with the same code, and output.resources is empty on all of them. The load error itself is in errorDetails.serviceError, not in msg. This failure is not transient, so the response[] entry is 4505 RESOURCE_LOAD_FAILED; a transient load failure is 5001 SYSTEM_ERROR, which is worth retrying.
Grant application partially applied¶
The resource changed, so the event has its counts and policy list, as a COMPLETED event would.
output.aclsExecutedshows how far the grants got: 1 of 3 ACL operations applied and 2 failed. That is a partial grant, so the outcome isGRANT_PARTIALLY_APPLIED, notGRANT_FAILED.- The target's error text is in
errorDetails.topReasons, and per policy inpolicies[].failedReasons. response[]lists both outcomes: one grant applied (2001), and two failed because the grantee does not exist (4503PRINCIPAL_NOT_FOUND), which a retry does not fix.
When every grant fails
An event where none of the ACL operations succeeded has the same shape but reports GRANT_FAILED with msg Failed to grant, and output.aclsExecuted shows applied: 0. The distinction matters when reading these events: GRANT_PARTIALLY_APPLIED means some access was granted, GRANT_FAILED means none was.
Validate¶
In Privacera Portal¶
The audits are indexed into Apache Solr, which is what Privacera Portal reads, so you can confirm the feature is working from the Portal without querying anything directly.
- Sign in to Privacera Portal.
- From the application menu at the top, select Access Controls.
- In the left navigation, select Audits.
- Select the Policy Sync tab.
- Adjust the date range at the top right if your sync is older than the default window, and use the search box to narrow the list.
- The tab lists every PolicySync event, so narrow it to the rows where Event Type is
ON_DEMAND.
Each row is one requested resource. Read the outcome from these columns:
| Column | What it shows |
|---|---|
| Event Type | ON_DEMAND for a requested sync |
| Status | COMPLETED or FAILED for that one resource |
| Message | The readable outcome, for example Resource not found in source; nothing to apply |
| Ondemand Task ID | Groups every resource from the same request |
| Time Taken | How long the sync took |
To review one request end to end, note its Ondemand Task ID and look at every row sharing that value.
If a sync produced no ON_DEMAND rows at all, the audit is most likely not enabled on the connector — check CONNECTOR_ON_DEMAND_AUDIT_ENABLE.
In Prometheus¶
The connector publishes one counter for on-demand audit emission, so you can confirm events are being produced — and spot events that failed to reach the audit pipeline at all, which by definition never reach the Portal or Solr.
| Metric | Description |
|---|---|
ps_core_ondemand_audit_events_total | One increment per ON_DEMAND audit event. Tagged connector, result, status, stage, and path |
| Tag | Values | Meaning |
|---|---|---|
connector | connector instance name | Which connector produced the event |
result | emitted, failed | Whether the event reached the audit pipeline |
status | COMPLETED, FAILED, none | The audit outcome the event reports |
stage | build, send, none | Where emission broke, if it did |
path | normal, processor_construction, stats_construction | How the emission was reached |
Useful queries:
| Text Only | |
|---|---|
Any non-zero result="failed" means an audit event was produced but never delivered — worth alerting on. A non-zero path!="normal" means something upstream failed while the audit was still emitted.
path is not an outcome
A fallback emission is still counted under result="emitted". Do not treat path as an alternative to success or failure, or the same events will be counted twice.
In Amazon SQS¶
Use the built-in test script inside the Audit Server diagnostics container to confirm ON_DEMAND records are arriving in your queue.
Warning
Setting DELETE_AFTER_PROCESSING to true permanently removes messages from the queue after the script reads them. Use false to inspect messages without consuming them.
- Open a shell inside the Audit Server diagnostics container.
- Navigate to the SQS test directory:
Bash - Set the required environment variables. Replace
<account-id>and<queue-name>with your AWS account ID and queue name. - Run the script:
Bash
The script prints the messages it reads. Look for records with "eventType":"ON_DEMAND" to confirm on-demand audits are flowing to the queue.
Guidelines¶
- The
ON_DEMANDaudit is per resource — an on-demand request for N resources produces N audit records. Keep this in mind when sizing a large batch, and watch the SQS/SNS 256 KB per-message limit for very large, policy-heavy syncs. - Build consumers against
extraInfo.response[], notmsg. New codes may be added across releases. See Response codes. - Enabling
CONNECTOR_ON_DEMAND_AUDIT_ENABLEdoes not change the Ops Server / task-status flow; that continues to work whether the flag is on or off. - If you filter an Amazon SNS subscription by event type, allow
eventType = ON_DEMAND— otherwise the on-demand events are dropped before they reach the subscribed queue even though the connector emitted them.
Applies to
Self-Managed Privacera only. PrivaceraCloud customers should contact their Privacera representative. This configuration applies to the per-resource ON_DEMAND audit on the policysync_audit collection.