diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c85461..19ded8e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
## [Unreleased]
+### Changed — direct core field access and audit protection
+- Remove all #1172/#1182 stand-ins and reflective bridges; use core field-access, classified-value token, audit protection, wipe flag and shredded-event APIs directly.
+- Register the resolver, key source and audit protector through core supplemental metadata.
+- Run core field-access integration and retention proof tests unconditionally.
+- Pin QQQ to `4.1.0-RC.7`, which ships field access (#1172) and audit value protection (#1182); all core stand-ins and reflective bridges are removed.
+
### Changed — RC.6 migration
- Pin QQQ to 4.1.0-RC.6; use released classification, client-channel and table-feature APIs directly.
- Fold UI API code and tests into the main build and remove the profile and reflective registration.
@@ -15,7 +21,7 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
- Atomic wipe batches (fields, markers, key deletions, audits, access log and run row in one rollback-capable transaction), per-record and per-table legal holds, a Review/Result "Run wipe now" process and a daily scheduled run.
- Per-record-field AES-GCM wrapped DEKs (AAD-bound) for crypto-shredding of audit tokens, `CHANGED_ONLY` audit treatment, and the audit-value protector.
- App-home warning when known backup retention exceeds a rule's `age_days`; `withBackupRetentionDays` and `withArchiveSink` options; README retention section and the backups residual risk.
-- Tests: all actions, holds, rollback, Review/Result, schedule, permissions, wrap/unwrap, and (on the SECUREDATA snapshot) the section 15 canary and wipe-proof tests.
+- Tests: all actions, holds, rollback, Review/Result, schedule, permissions, wrap/unwrap, and the section 15 canary and wipe-proof tests.
### Fixed
- The key source returned a DEK array it then zeroed; it now returns copies, with a regression test.
diff --git a/README.md b/README.md
index 76f6226..88550a5 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,11 @@ Phases (b), (c), (e) and (f) of [SECURED-DATA](https://github.com/QRun-IO/qqq/bl
## Compatibility
-Java 21, Maven wrapper 3.9.11, **QQQ 4.1.0-RC.6**.
+Java 21, Maven wrapper 3.9.11, and **QQQ 4.1.0-RC.7** or later (it ships field access #1172 and audit value protection #1182).
-RC.6 supplies core classifications, AI metadata, client channels, table-feature permissions and the UI API extension points. These are used directly; the UI API and its contract test are part of the main build. The installed middleware jar provides `UiApiContract`, but no `UiApiContractAssertions` facade, so the contract test calls that released API directly.
+Core classifications, AI metadata, client channels, table-feature permissions and UI API extension points are used directly. The UI API and its contract test are part of the main build; the contract test calls `UiApiContract` directly.
-Field access (#1172) and audit-value protection (#1182) are not in RC.6. Field-access stand-ins and `CoreFieldAccessBridge` remain until the first QQQ RC that ships both #1172 and #1182 (expected RC.8). RC.6 **does not enforce field tiers**. Integration tests can use `4.1.0-SECUREDATA-SNAPSHOT`.
+Field access (#1172), classified-value tokens and audit-value protection (#1182) use core types directly. The producer registers the resolver, key source and audit protector through core supplemental metadata; QQQ core enforces field tiers.
## Install and register
@@ -16,7 +16,7 @@ Field access (#1172) and audit-value protection (#1182) are not in RC.6. Field-a
com.kingsrook.qbits
qbit-secured-data
- 1.0.0-SNAPSHOT
+ 0.1.0-RC.1
```
@@ -159,10 +159,9 @@ Configure before producing the QBit; runtime infrastructure and HMAC keys are ca
```powershell
./mvnw.cmd -q -o clean verify
-./mvnw.cmd -q -o clean verify -Dqqq.snapshot.version=4.1.0-SECUREDATA-SNAPSHOT
```
-The direct suite uses memory and H2. `CoreFieldAccessBridgeIT` is included by Surefire and uses JUnit assumptions to skip unless the actual core resolver metadata is present. With the installed core snapshot it exercises Query/Get, policy tiers, masking, exact matches and outcomes, sorting denial, rate/entropy refusal and the assistant wall. Checkstyle and JaCoCo remain 70% instructions / 90% classes; stand-ins are not excluded.
+The suite uses memory and H2. `CoreFieldAccessIT` runs unconditionally through Surefire and exercises Query/Get, policy tiers, masking, exact matches and outcomes, sorting denial, rate/entropy refusal and the assistant wall. `RetentionProofTest` also runs unconditionally. Checkstyle and JaCoCo gates remain 70% instructions / 90% classes.
## Anomaly monitoring (phase e)
@@ -243,11 +242,11 @@ Each is read from a JVM property or the environment and is never logged. Losing
### Core hooks
-Audit-value protection (QRun-IO/qqq#1182) and the wipe audit flag need core classes that are not in RC.6. On RC.6 the qbit uses marked stand-ins (`removeBy=RC.8`), writes value-free wipe audit details itself, and core does not tokenise ordinary audits. Run the build with `-Dqqq.snapshot.version=4.1.0-SECUREDATA-SNAPSHOT` to exercise the real hooks, including the section 15 canary and wipe-proof tests (`RetentionProofTest`), which skip themselves when the hooks are absent.
+Audit-value protection, wipe audit flags and shredded audit events use the core #1182 APIs directly. Core tokenises ordinary audits and writes value-free wipe details. The section 15 canary and wipe-proof tests (`RetentionProofTest`) are mandatory in the normal build.
## Remaining phases
-RC.6 migration is complete; field-access and audit-token compatibility types remain until RC.8.
+Field-access and audit-token stand-ins and reflective bridges have been removed. The coordinator will replace the temporary snapshot pin with the first QQQ RC containing #1172 and #1182.
## License
diff --git a/pom.xml b/pom.xml
index a5bba5d..b6b548a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,9 +22,9 @@
-
- 4.1.0-RC.6
- 1.0.0-SNAPSHOT
+
+ 4.1.0-RC.7
+ 0.1.0-SNAPSHOT
21
21
0.8.15
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/CoreAuditBridge.java b/src/main/java/com/kingsrook/qbits/secureddata/CoreAuditBridge.java
deleted file mode 100644
index bbb4a57..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/CoreAuditBridge.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2025. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata;
-
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Proxy;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource;
-import com.kingsrook.qbits.secureddata.standin.ClassifiedValueTokens;
-import com.kingsrook.qbits.secureddata.standin.DMLType;
-import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
-import com.kingsrook.qqq.backend.core.actions.audits.AuditAction;
-import com.kingsrook.qqq.backend.core.exceptions.QException;
-import com.kingsrook.qqq.backend.core.model.actions.audits.AuditInput;
-import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
-import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
-import com.kingsrook.qqq.backend.core.model.data.QRecord;
-import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-
-/*******************************************************************************
- ** A single reflection boundary for #1182, failing closed for incompatible hooks.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
-public final class CoreAuditBridge
-{
- private static final String TOKENS = "com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.";
- private static final String AUDITS = "com.kingsrook.qqq.backend.core.actions.audits.";
-
- /*******************************************************************************
- ** Utility class.
- *******************************************************************************/
- private CoreAuditBridge()
- {
- }
-
- /*******************************************************************************
- ** Core hook availability (RC.6 has none).
- *******************************************************************************/
- public static boolean available()
- {
- try
- {
- Class.forName(TOKENS + "ClassifiedValueTokens");
- return (true);
- }
- catch(ClassNotFoundException expected)
- {
- return (false);
- }
- }
-
- /*******************************************************************************
- ** Register exact #1182 contracts when present; no silent partial registration.
- *******************************************************************************/
- public static void register(QInstance instance, SecuredDataRuntime runtime)
- {
- if(!available())
- {
- return;
- }
- try
- {
- Class> contract = Class.forName(TOKENS + "ClassifiedValueKeySource");
- Object keyProxy = Proxy.newProxyInstance(contract.getClassLoader(), new Class>[] {contract}, (self, method, args) ->
- {
- if(method.getDeclaringClass() == Object.class)
- {
- return (objectMethod(self, method.getName(), args));
- }
- Optional key = switch(method.getName())
- {
- case "instanceKey" -> runtime.keys().instanceKey();
- case "keyForRecordField" -> runtime.keys().keyForRecordField((String) args[0], (String) args[1], (Serializable) args[2], (Boolean) args[3]);
- default -> throw new UnsupportedOperationException(method.getName());
- };
- return (key.isEmpty() ? Optional.empty() : Optional.of(Class.forName(TOKENS + "ClassifiedValueKeySource$KeyAndId")
- .getConstructor(byte[].class, String.class).newInstance(key.get().key(), key.get().id())));
- });
- Class> metadata = Class.forName(TOKENS + "ClassifiedValueKeySourceMetaData");
- Object registry = metadata.getMethod("ofOrWithNew", QInstance.class).invoke(null, instance);
- metadata.getMethod("withKeySourceInstance", contract).invoke(registry, keyProxy);
- SecuredDataAuditProtector protector = new SecuredDataAuditProtector();
- Class> auditContract = Class.forName(AUDITS + "AuditValueProtectorInterface");
- Object auditProxy = Proxy.newProxyInstance(auditContract.getClassLoader(), new Class>[] {auditContract}, (self, method, args) ->
- {
- if(method.getDeclaringClass() == Object.class)
- {
- return (objectMethod(self, method.getName(), args));
- }
- if("classificationOf".equals(method.getName()))
- {
- return (protector.classificationOf((QTableMetaData) args[0], (QFieldMetaData) args[1]));
- }
- if(!"protect".equals(method.getName()))
- {
- throw (new UnsupportedOperationException(method.getName()));
- }
- var values = protector.protect((QTableMetaData) args[0], (QFieldMetaData) args[1], (Serializable) args[2], (Serializable) args[3], (Serializable) args[4], DMLType.valueOf(args[5].toString()));
- return (Class.forName(AUDITS + "ProtectedAuditValues").getConstructor(String.class, String.class, String.class)
- .newInstance(values.oldToken(), values.newToken(), values.message()));
- });
- metadata = Class.forName(AUDITS + "AuditValueProtectionMetaData");
- registry = metadata.getMethod("ofOrWithNew", QInstance.class).invoke(null, instance);
- metadata.getMethod("withProtectorInstance", auditContract).invoke(registry, auditProxy);
- }
- catch(ReflectiveOperationException e)
- {
- throw (new IllegalStateException("Unable to register core audit protection", e));
- }
- }
-
- /*******************************************************************************
- ** Stable object behavior for registered service proxies.
- *******************************************************************************/
- private static Object objectMethod(Object self, String name, Object[] args)
- {
- return (switch(name)
- {
- case "toString" -> "Secured-data audit bridge";
- case "hashCode" -> System.identityHashCode(self);
- case "equals" -> self == args[0];
- default -> throw new UnsupportedOperationException(name);
- });
- }
-
- /*******************************************************************************
- ** Delegate to the installed core helper, with contract-identical RC.6 fallback.
- *******************************************************************************/
- public static String token(QTableMetaData table, QFieldMetaData field, Serializable pk, Serializable value)
- {
- if(!available())
- {
- return (ClassifiedValueTokens.forRecordField(table, field, pk, value));
- }
- try
- {
- return ((String) Class.forName(TOKENS + "ClassifiedValueTokens").getMethod("forRecordField", QTableMetaData.class, QFieldMetaData.class, Serializable.class, Serializable.class)
- .invoke(null, table, field, pk, value));
- }
- catch(InvocationTargetException e)
- {
- throw (new IllegalStateException("Classified token creation failed"));
- }
- catch(ReflectiveOperationException e)
- {
- throw (new IllegalStateException("Incompatible core token API", e));
- }
- }
-
- /*******************************************************************************
- ** Real WipeAuditFlag on snapshot; RC.6 uses a value-free explicit audit below.
- *******************************************************************************/
- public static void flag(UpdateInput input, String ruleId)
- {
- if(!available())
- {
- input.setOmitDmlAudit(true);
- return;
- }
- try
- {
- ActionFlag flag = (ActionFlag) Class.forName("com.kingsrook.qqq.backend.core.model.actions.tables.WipeAuditFlag").getConstructor(String.class).newInstance(ruleId);
- input.setFlags(Set.of(flag));
- }
- catch(ReflectiveOperationException e)
- {
- throw (new IllegalStateException("Incompatible core wipe API", e));
- }
- }
-
- /*******************************************************************************
- ** Append immutable value-free audit evidence in the batch transaction.
- *******************************************************************************/
- public static void detail(QTableMetaData table, Serializable pk, String field, String ruleId, boolean shredded, QBackendTransaction transaction) throws QException
- {
- if(shredded && available())
- {
- try
- {
- Class.forName(AUDITS + "AuditShreddedEvent").getMethod("shreddedDetail", QTableMetaData.class, Serializable.class, QFieldMetaData.class, String.class, QBackendTransaction.class)
- .invoke(null, table, pk, table.getField(field), ruleId, transaction);
- return;
- }
- catch(ReflectiveOperationException e)
- {
- throw (new QException("Unable to append shredded audit detail"));
- }
- }
- String message = (shredded ? "Audit values shredded" : "Wiped") + " per policy";
- QRecord detail = new QRecord().withValue("fieldName", field).withValue("newValue", (shredded ? "shredded:v1:" : "wiped:v1:") + ruleId).withValue("message", message);
- AuditInput input = new AuditInput();
- input.setTransaction(transaction);
- AuditAction.appendToInput(input, table.getName(), pk, AuditAction.getRecordSecurityKeyValues(table,
- new QRecord().withValue(table.getPrimaryKeyField(), pk), Optional.empty()), message, List.of(detail));
- new AuditAction().execute(input);
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/CoreFieldAccessBridge.java b/src/main/java/com/kingsrook/qbits/secureddata/CoreFieldAccessBridge.java
deleted file mode 100644
index dd2a424..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/CoreFieldAccessBridge.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2025. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata;
-
-
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Proxy;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessDecision;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessEvent;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessResolverInterface;
-import com.kingsrook.qqq.backend.core.logging.QLogger;
-import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-
-
-/*******************************************************************************
- ** The only boundary between the temporary field-access API and unreleased core.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8)
-public final class CoreFieldAccessBridge
-{
- private static final String PACKAGE = "com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.";
- private static final QLogger LOG = QLogger.getLogger(CoreFieldAccessBridge.class);
- private static final AtomicBoolean WARNED = new AtomicBoolean();
-
-
-
- /*******************************************************************************
- ** Utility class.
- *******************************************************************************/
- private CoreFieldAccessBridge()
- {
- }
-
-
-
- /*******************************************************************************
- ** Register only when core can actually enforce field access.
- *******************************************************************************/
- public static boolean register(QInstance instance, FieldAccessResolverInterface resolver)
- {
- try
- {
- Class> metadata = Class.forName(PACKAGE + "FieldAccessResolverMetaData");
- Class> contract = Class.forName(PACKAGE + "FieldAccessResolverInterface");
- Object proxy = Proxy.newProxyInstance(contract.getClassLoader(), new Class>[] {contract}, (self, method, args) ->
- {
- return (switch(method.getName())
- {
- case "resolve" -> decision(resolver.resolve((QTableMetaData) args[0], (QFieldMetaData) args[1]));
- case "getAccessFingerprint" -> resolver.getAccessFingerprint();
- case "isRevealActive" -> resolver.isRevealActive((String) args[0], (String) args[1], (Serializable) args[2]);
- case "fingerprintSearchValue" -> resolver.fingerprintSearchValue((Serializable) args[0]);
- case "checkExactMatch" ->
- {
- resolver.checkExactMatch(event(args[0]));
- yield null;
- }
- case "onAccess" ->
- {
- resolver.onAccess(event(args[0]));
- yield null;
- }
- case "toString" -> "SecuredData core bridge";
- case "hashCode" -> System.identityHashCode(self);
- case "equals" -> self == args[0];
- default -> throw new UnsupportedOperationException(method.getName());
- });
- });
- Object registry = metadata.getMethod("ofOrWithNew", QInstance.class).invoke(null, instance);
- metadata.getMethod("withResolverInstance", contract).invoke(registry, proxy);
- return (true);
- }
- catch(ClassNotFoundException e)
- {
- if(WARNED.compareAndSet(false, true))
- {
- LOG.info("Secured-data field enforcement needs QQQ RC.6+; policy and access-log services are available.");
- }
- return (false);
- }
- catch(ReflectiveOperationException e)
- {
- throw (new IllegalStateException("Core field-access API is incompatible", unwrap(e)));
- }
- }
-
-
-
- /*******************************************************************************
- ** Construct the core record by its declared component types and enum names.
- *******************************************************************************/
- private static Object decision(FieldAccessDecision decision) throws ReflectiveOperationException
- {
- Class> type = Class.forName(PACKAGE + "FieldAccessDecision");
- Class> tier = type.getRecordComponents()[0].getType();
- Class> mask = type.getRecordComponents()[1].getType();
- return (type.getConstructor(tier, mask, String.class, boolean.class).newInstance(
- enumValue(tier, decision.tier().name()), decision.maskStyle() == null ? null : enumValue(mask, decision.maskStyle().name()),
- decision.reason(), decision.exactMatchAllowed()));
- }
-
-
-
- /*******************************************************************************
- ** Convert enum types without sharing class identity across the bridge.
- *******************************************************************************/
- private static Object enumValue(Class> type, String name) throws ReflectiveOperationException
- {
- return (type.getMethod("valueOf", String.class).invoke(null, name));
- }
-
-
-
- /*******************************************************************************
- ** Events carry identifiers and keyed fingerprints, never search literals.
- *******************************************************************************/
- @SuppressWarnings("unchecked")
- private static FieldAccessEvent event(Object source) throws ReflectiveOperationException
- {
- Class> type = source.getClass();
- Object outcome = type.getMethod("getOutcome").invoke(source);
- return (new FieldAccessEvent()
- .withType(FieldAccessEventType.valueOf(type.getMethod("getType").invoke(source).toString()))
- .withTableName((String) type.getMethod("getTableName").invoke(source))
- .withFieldNames((List) type.getMethod("getFieldNames").invoke(source))
- .withPrimaryKeys((List) type.getMethod("getPrimaryKeys").invoke(source))
- .withRecordCount((Integer) type.getMethod("getRecordCount").invoke(source))
- .withActionName((String) type.getMethod("getActionName").invoke(source))
- .withOutcome(outcome == null ? null : FieldAccessOutcome.valueOf(outcome.toString()))
- .withSearchValueFingerprints((List) type.getMethod("getSearchValueFingerprints").invoke(source)));
- }
-
-
-
- /*******************************************************************************
- ** Preserve the startup failure, including duplicate resolver registration.
- *******************************************************************************/
- private static Throwable unwrap(ReflectiveOperationException exception)
- {
- return (exception instanceof InvocationTargetException invocation ? invocation.getCause() : exception);
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLogger.java b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLogger.java
index 8cf0822..0e9c806 100644
--- a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLogger.java
+++ b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLogger.java
@@ -38,11 +38,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessEvent;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
import com.kingsrook.qbits.secureddata.support.SystemWrites;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEvent;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome;
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
import com.kingsrook.qqq.backend.core.alerts.QAlerts;
import com.kingsrook.qqq.backend.core.context.CapturedContext;
@@ -60,7 +60,6 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
-
/*******************************************************************************
** Bounded asynchronous value-free audit, with persisted and local enumeration accounting.
*******************************************************************************/
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAuditProtector.java b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAuditProtector.java
index a3630e1..c290f43 100644
--- a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAuditProtector.java
+++ b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataAuditProtector.java
@@ -22,10 +22,11 @@ package com.kingsrook.qbits.secureddata;
import java.io.Serializable;
import com.kingsrook.qbits.secureddata.retention.RetentionRules;
-import com.kingsrook.qbits.secureddata.standin.AuditValueProtectorInterface;
-import com.kingsrook.qbits.secureddata.standin.DMLType;
-import com.kingsrook.qbits.secureddata.standin.ProtectedAuditValues;
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
+import com.kingsrook.qqq.backend.core.actions.audits.AuditValueProtectorInterface;
+import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction.DMLType;
+import com.kingsrook.qqq.backend.core.actions.audits.ProtectedAuditValues;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@@ -56,7 +57,7 @@ public final class SecuredDataAuditProtector implements AuditValueProtectorInter
{
boolean changed = RetentionRules.changedOnly(table.getName(), field.getName());
String message = (oldValue == null ? "Set " : newValue == null ? "Removed " : "Changed ") + field.getLabel();
- return (new ProtectedAuditValues(oldValue == null ? null : changed ? "changed:v1" : CoreAuditBridge.token(table, field, pk, oldValue),
- newValue == null ? null : changed ? "changed:v1" : CoreAuditBridge.token(table, field, pk, newValue), message));
+ return (new ProtectedAuditValues(oldValue == null ? null : changed ? "changed:v1" : ClassifiedValueTokens.forRecordField(table, field, pk, oldValue),
+ newValue == null ? null : changed ? "changed:v1" : ClassifiedValueTokens.forRecordField(table, field, pk, newValue), message));
}
}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataKeySource.java b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataKeySource.java
index cdf6c15..ac34362 100644
--- a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataKeySource.java
+++ b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataKeySource.java
@@ -37,9 +37,10 @@ import java.util.Map;
import java.util.Optional;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
import com.kingsrook.qbits.secureddata.retention.RetentionRules;
-import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource;
import com.kingsrook.qbits.secureddata.support.SystemWrites;
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueKeySource;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@@ -280,7 +281,7 @@ public final class SecuredDataKeySource implements ClassifiedValueKeySource, Aut
instanceOnly.set(true);
try
{
- return (CoreAuditBridge.token(table, field, pk, value));
+ return (ClassifiedValueTokens.forRecordField(table, field, pk, value));
}
finally
{
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitConfig.java b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitConfig.java
index 10e5f8a..edc18da 100644
--- a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitConfig.java
+++ b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitConfig.java
@@ -29,13 +29,12 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-
/*******************************************************************************
** Configuration for policy resolution and bounded asynchronous access logging.
*******************************************************************************/
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducer.java b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducer.java
index f9041f0..4d228d0 100644
--- a/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducer.java
+++ b/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducer.java
@@ -29,7 +29,10 @@ import com.kingsrook.qbits.secureddata.metadata.SecuredDataTableCustomizers;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
+import com.kingsrook.qqq.backend.core.actions.audits.AuditValueProtectionMetaData;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueKeySourceMetaData;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessResolverMetaData;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@@ -39,7 +42,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-
/*******************************************************************************
** Entry point for secured-data policy, classification and access logging.
*******************************************************************************/
@@ -47,7 +49,7 @@ public class SecuredDataQBitProducer implements QBitMetaDataProducer instanceKey();
-
- /*******************************************************************************
- ** Verification uses create=false and must never recreate a destroyed key.
- *******************************************************************************/
- Optional keyForRecordField(String table, String field, Serializable pk, boolean create);
-
- /*******************************************************************************
- ** Key bytes and the public identifier stored in a token.
- *******************************************************************************/
- record KeyAndId(byte[] key, String id)
- {
- /*******************************************************************************
- ** Own the key bytes; identifiers must keep tokens ASCII and under 200 characters.
- *******************************************************************************/
- public KeyAndId
- {
- if(key == null || key.length == 0 || id == null || !id.matches("[A-Za-z0-9._-]{1,140}"))
- {
- throw new IllegalArgumentException("Invalid classified-value key or identifier");
- }
- key = key.clone();
- }
-
- /*******************************************************************************
- ** Do not expose mutable key material.
- *******************************************************************************/
- @Override
- public byte[] key()
- {
- return (key.clone());
- }
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/ClassifiedValueTokens.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/ClassifiedValueTokens.java
deleted file mode 100644
index 41dd7ee..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/ClassifiedValueTokens.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.util.Base64;
-import java.util.List;
-import java.util.Optional;
-import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource.KeyAndId;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-import com.kingsrook.qqq.backend.core.utils.ValueUtils;
-
-
-/*******************************************************************************
- ** Shared, versioned audit/email value tokens. The MAC input is UTF-8.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
-public final class ClassifiedValueTokens
-{
-
- /*******************************************************************************
- ** Static utility.
- *******************************************************************************/
- private ClassifiedValueTokens()
- {
- }
-
-
-
- /*******************************************************************************
- ** Bind a value to the table, field and primary key, preferring a shreddable key.
- *******************************************************************************/
- public static String forRecordField(QTableMetaData table, QFieldMetaData field, Serializable pk, Serializable value)
- {
- if(value == null)
- {
- return null;
- }
- ClassifiedValueKeySource source = source();
- Optional key = source.keyForRecordField(table.getName(), field.getName(), pk, true);
- return key.isPresent() ? token("shred", key.get(), binding(table, field, pk), value)
- : instanceToken(source, binding(table, field, pk), value);
- }
-
-
-
- /*******************************************************************************
- ** Non-record bindings must start with a nonempty domain.
- *******************************************************************************/
- public static String forContext(List bindingParts, Serializable value)
- {
- validateDomain(bindingParts);
- return value == null ? null : instanceToken(source(), bindingParts, value);
- }
-
-
-
- /*******************************************************************************
- ** No derivative of the original value.
- *******************************************************************************/
- public static String changed()
- {
- return "changed:v1";
- }
-
-
-
- /*******************************************************************************
- ** A key-destruction event, never a derivative of the value.
- *******************************************************************************/
- public static String shreddedToken(String ruleId)
- {
- if(ruleId == null || !ruleId.matches("[A-Za-z0-9._-]{1,180}"))
- {
- throw new IllegalArgumentException("Invalid shred rule identifier");
- }
- return "shredded:v1:" + ruleId;
- }
-
-
-
- /*******************************************************************************
- ** Verify only keyed tokens, using existing keys and constant-time MAC comparison.
- *******************************************************************************/
- public static boolean verifyRecordField(QTableMetaData table, QFieldMetaData field, Serializable pk, Serializable candidate, String token)
- {
- if(candidate == null || token == null)
- {
- return false;
- }
- ClassifiedValueKeySource source = source();
- if(token.startsWith("shred:v1:"))
- {
- return verify(source.keyForRecordField(table.getName(), field.getName(), pk, false), "shred", binding(table, field, pk), candidate, token);
- }
- return verify(source.instanceKey(), "hmac", binding(table, field, pk), candidate, token);
- }
-
-
-
- /*******************************************************************************
- ** Verify a non-record token without creating a key.
- *******************************************************************************/
- public static boolean verifyContext(List bindingParts, Serializable candidate, String token)
- {
- validateDomain(bindingParts);
- return candidate != null && token != null && verify(source().instanceKey(), "hmac", bindingParts, candidate, token);
- }
-
-
-
- /*******************************************************************************
- ** Canonical ValueUtils strings, with scale-independent plain decimal notation.
- *******************************************************************************/
- public static String canonical(Serializable value)
- {
- return value instanceof BigDecimal decimal ? decimal.stripTrailingZeros().toPlainString() : ValueUtils.getValueAsString(value);
- }
-
-
-
- /*******************************************************************************
- ** Validate the context even for absent values.
- *******************************************************************************/
- private static void validateDomain(List parts)
- {
- if(parts == null || parts.isEmpty() || parts.get(0) == null || parts.get(0).isBlank() || parts.stream().anyMatch(p -> p == null || p.indexOf('\u001F') >= 0))
- {
- throw new IllegalArgumentException("A nonempty domain and unambiguous binding parts are required");
- }
- }
-
-
-
- /*******************************************************************************
- ** The agreed record binding does not prepend an additional domain.
- *******************************************************************************/
- private static List binding(QTableMetaData table, QFieldMetaData field, Serializable pk)
- {
- return List.of(table.getName(), field.getName(), String.valueOf(pk));
- }
-
-
-
- /*******************************************************************************
- ** Emit a keyed token or a value-free fallback.
- *******************************************************************************/
- private static String instanceToken(ClassifiedValueKeySource source, List parts, Serializable value)
- {
- return source.instanceKey().map(key -> token("hmac", key, parts, value)).orElseGet(ClassifiedValueTokens::changed);
- }
-
-
-
- /*******************************************************************************
- ** Compare full tokens, including the key identifier and version.
- *******************************************************************************/
- private static boolean verify(Optional key, String kind, List parts, Serializable value, String supplied)
- {
- return key.map(k -> MessageDigest.isEqual(token(kind, k, parts, value).getBytes(StandardCharsets.UTF_8), supplied.getBytes(StandardCharsets.UTF_8))).orElse(false);
- }
-
-
-
- /*******************************************************************************
- ** Encode the HMAC with URL-safe base64 and no padding.
- *******************************************************************************/
- private static String token(String kind, KeyAndId key, List parts, Serializable value)
- {
- try
- {
- Mac mac = Mac.getInstance("HmacSHA256");
- mac.init(new SecretKeySpec(key.key(), "HmacSHA256"));
- byte[] digest = mac.doFinal((String.join("\u001F", parts) + "\u001F" + canonical(value)).getBytes(StandardCharsets.UTF_8));
- return kind + ":v1:" + key.id() + ":" + Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
- }
- catch(GeneralSecurityException e)
- {
- throw new IllegalStateException("Unable to compute classified-value token", e);
- }
- }
-
-
-
- /*******************************************************************************
- ** Use instance metadata, falling back to environment/property configuration.
- *******************************************************************************/
- private static ClassifiedValueKeySource source()
- {
- return (com.kingsrook.qbits.secureddata.SecuredDataRuntime.current().keys());
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/DMLType.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/DMLType.java
deleted file mode 100644
index e6d1d68..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/DMLType.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2025. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-/*******************************************************************************
- ** RC.6's nested DMLType is package-private; #1182 makes it public.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
-public enum DMLType
-{
- INSERT, UPDATE, DELETE, OTHER
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessDecision.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessDecision.java
deleted file mode 100644
index a071d3a..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessDecision.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-
-
-/*******************************************************************************
- ** A resolver decision; the reason must never contain a field value.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessDecision; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
-public record FieldAccessDecision(FieldAccessTier tier, FieldMaskStyle maskStyle, String reason, boolean exactMatchAllowed)
-{
- public static final FieldAccessDecision FULL = new FieldAccessDecision(FieldAccessTier.FULL, null, null);
-
-
-
- /*******************************************************************************
- ** Compatibility constructor: exact-match searches are allowed by default.
- *******************************************************************************/
- public FieldAccessDecision(FieldAccessTier tier, FieldMaskStyle maskStyle, String reason)
- {
- this(tier, maskStyle, reason, true);
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessEvent.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessEvent.java
deleted file mode 100644
index f731129..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessEvent.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-
-/*******************************************************************************
- ** Value-free audit notification. Primary key samples contain at most 50 keys.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEvent; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
-public class FieldAccessEvent
-{
- private FieldAccessEventType type;
- private String tableName;
- private List fieldNames;
- private List primaryKeys;
- private Integer recordCount;
- private String actionName;
- private FieldAccessOutcome outcome;
- private List searchValueFingerprints;
-
-
-
- /*******************************************************************************
- ** Getter for type
- *******************************************************************************/
- public FieldAccessEventType getType()
- {
- return (this.type);
- }
-
-
-
- /*******************************************************************************
- ** Setter for type
- *******************************************************************************/
- public void setType(FieldAccessEventType type)
- {
- this.type = type;
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for type
- *******************************************************************************/
- public FieldAccessEvent withType(FieldAccessEventType type)
- {
- setType(type);
- return (this);
- }
-
-
-
- /*******************************************************************************
- ** Getter for tableName
- *******************************************************************************/
- public String getTableName()
- {
- return (this.tableName);
- }
-
-
-
- /*******************************************************************************
- ** Setter for tableName
- *******************************************************************************/
- public void setTableName(String tableName)
- {
- this.tableName = tableName;
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for tableName
- *******************************************************************************/
- public FieldAccessEvent withTableName(String tableName)
- {
- setTableName(tableName);
- return (this);
- }
-
-
-
- /*******************************************************************************
- ** Getter for fieldNames
- *******************************************************************************/
- public List getFieldNames()
- {
- return (this.fieldNames);
- }
-
-
-
- /*******************************************************************************
- ** Setter for fieldNames
- *******************************************************************************/
- public void setFieldNames(List fieldNames)
- {
- this.fieldNames = fieldNames == null ? null : new ArrayList<>(fieldNames);
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for fieldNames
- *******************************************************************************/
- public FieldAccessEvent withFieldNames(List fieldNames)
- {
- setFieldNames(fieldNames);
- return (this);
- }
-
-
-
- /*******************************************************************************
- ** Getter for primaryKeys
- *******************************************************************************/
- public List getPrimaryKeys()
- {
- return (this.primaryKeys == null ? null : Collections.unmodifiableList(this.primaryKeys));
- }
-
-
-
- /*******************************************************************************
- ** Setter for primaryKeys
- *******************************************************************************/
- public void setPrimaryKeys(List primaryKeys)
- {
- this.primaryKeys = primaryKeys == null ? null : new ArrayList<>(primaryKeys.subList(0, Math.min(50, primaryKeys.size())));
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for primaryKeys
- *******************************************************************************/
- public FieldAccessEvent withPrimaryKeys(List primaryKeys)
- {
- setPrimaryKeys(primaryKeys);
- return (this);
- }
-
-
-
- /*******************************************************************************
- ** Getter for recordCount
- *******************************************************************************/
- public Integer getRecordCount()
- {
- return (this.recordCount);
- }
-
-
-
- /*******************************************************************************
- ** Setter for recordCount
- *******************************************************************************/
- public void setRecordCount(Integer recordCount)
- {
- this.recordCount = recordCount;
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for recordCount
- *******************************************************************************/
- public FieldAccessEvent withRecordCount(Integer recordCount)
- {
- setRecordCount(recordCount);
- return (this);
- }
-
-
-
- /*******************************************************************************
- ** Getter for actionName
- *******************************************************************************/
- public String getActionName()
- {
- return (this.actionName);
- }
-
-
-
- /*******************************************************************************
- ** Setter for actionName
- *******************************************************************************/
- public void setActionName(String actionName)
- {
- this.actionName = actionName;
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for actionName
- *******************************************************************************/
- public FieldAccessEvent withActionName(String actionName)
- {
- setActionName(actionName);
- return (this);
- }
-
-
-
- /*******************************************************************************
- ** Getter for outcome
- *******************************************************************************/
- public FieldAccessOutcome getOutcome()
- {
- return (outcome);
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for outcome
- *******************************************************************************/
- public FieldAccessEvent withOutcome(FieldAccessOutcome outcome)
- {
- this.outcome = outcome;
- return (this);
- }
-
-
-
- /*******************************************************************************
- ** Getter for searchValueFingerprints
- *******************************************************************************/
- public List getSearchValueFingerprints()
- {
- return (searchValueFingerprints);
- }
-
-
-
- /*******************************************************************************
- ** Fluent setter for searchValueFingerprints
- *******************************************************************************/
- public FieldAccessEvent withSearchValueFingerprints(List searchValueFingerprints)
- {
- this.searchValueFingerprints = searchValueFingerprints == null ? null : List.copyOf(searchValueFingerprints);
- return (this);
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessEventType.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessEventType.java
deleted file mode 100644
index 496ae57..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessEventType.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-
-
-/*******************************************************************************
- ** Kinds of field access reported to the resolver.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
-public enum FieldAccessEventType
-{
- VIEW, EXACT_MATCH_FILTER, DENIED
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessOutcome.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessOutcome.java
deleted file mode 100644
index 0297ab8..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessOutcome.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-
-
-/*******************************************************************************
- ** Outcome of an exact-match search.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
-public enum FieldAccessOutcome
-{
- MATCH, NO_MATCH, REFUSED
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessResolverInterface.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessResolverInterface.java
deleted file mode 100644
index 93b2a01..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessResolverInterface.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-import java.io.Serializable;
-import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
-import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
-import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
-
-
-/*******************************************************************************
- ** Session-aware policy, reveal grants and value-free access notifications.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessResolverInterface; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
-public interface FieldAccessResolverInterface
-{
- /*******************************************************************************
- ** Resolve using the current QContext session.
- *******************************************************************************/
- FieldAccessDecision resolve(QTableMetaData table, QFieldMetaData field);
-
-
-
- /*******************************************************************************
- ** Whether this record and field have an active reveal grant.
- *******************************************************************************/
- default boolean isRevealActive(String tableName, String fieldName, Serializable primaryKey)
- {
- return (false);
- }
-
-
-
- /*******************************************************************************
- ** Stable identity of effective access, excluding reveal grants.
- *******************************************************************************/
- default String getAccessFingerprint()
- {
- return ("");
- }
-
-
-
- /*******************************************************************************
- ** Receive an audit notification; failures never prevent enforcement.
- *******************************************************************************/
- default void onAccess(FieldAccessEvent event)
- {
-
- }
-
-
-
- /*******************************************************************************
- ** Refuse an exact-match search before execution; the exception message is user-facing.
- *******************************************************************************/
- default void checkExactMatch(FieldAccessEvent event) throws QPermissionDeniedException
- {
-
- }
-
-
-
- /*******************************************************************************
- ** One-way fingerprint of a search literal. Raw values never enter access events.
- *******************************************************************************/
- default String fingerprintSearchValue(Serializable value)
- {
- return (null);
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessTier.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessTier.java
deleted file mode 100644
index 6cd36d8..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldAccessTier.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
-
-
-/*******************************************************************************
- ** Effective field access, ordered from least to most restrictive.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
-public enum FieldAccessTier
-{
- FULL, REVEALABLE, MASKED, HIDDEN;
-
-
-
- /*******************************************************************************
- ** canReadValue
- *******************************************************************************/
- public boolean canReadValue()
- {
- return (this == FULL);
- }
-
-
-
- /*******************************************************************************
- ** canSort
- *******************************************************************************/
- public boolean canSort()
- {
- return (this == FULL);
- }
-
-
-
- /*******************************************************************************
- ** canGroupOrAggregate
- *******************************************************************************/
- public boolean canGroupOrAggregate()
- {
- return (this == FULL);
- }
-
-
-
- /*******************************************************************************
- ** canQuickSearch
- *******************************************************************************/
- public boolean canQuickSearch()
- {
- return (this == FULL);
- }
-
-
-
- /*******************************************************************************
- ** canWrite
- *******************************************************************************/
- public boolean canWrite()
- {
- return (this == FULL);
- }
-
-
-
- /*******************************************************************************
- ** Whether the operator is available for this tier.
- *******************************************************************************/
- public boolean canFilter(QCriteriaOperator operator)
- {
- return (this == FULL || (this == REVEALABLE && (operator == QCriteriaOperator.EQUALS || operator == QCriteriaOperator.IN)));
- }
-
-
-
- /*******************************************************************************
- ** Whether the field may be described to the caller.
- *******************************************************************************/
- public boolean isInMetaData()
- {
- return (this != HIDDEN);
- }
-
-
-
- /*******************************************************************************
- ** Combine tiers; null means FULL.
- *******************************************************************************/
- public FieldAccessTier mostRestrictive(FieldAccessTier other)
- {
- FieldAccessTier candidate = other == null ? FULL : other;
- return (ordinal() > candidate.ordinal() ? this : candidate);
- }
-
-
-
- /*******************************************************************************
- ** Combine tiers; null means FULL.
- *******************************************************************************/
- public FieldAccessTier leastRestrictive(FieldAccessTier other)
- {
- FieldAccessTier candidate = other == null ? FULL : other;
- return (ordinal() < candidate.ordinal() ? this : candidate);
- }
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldMaskStyle.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldMaskStyle.java
deleted file mode 100644
index f0f3862..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/FieldMaskStyle.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-
-
-/*******************************************************************************
- ** Presentation rules for a restricted value.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1172, removeBy=RC.8): copy of com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldMaskStyle; delete when the pin moves to the first QQQ RC with #1172 and #1182 (RC.8).
-public enum FieldMaskStyle
-{
- AUTO, EMAIL, LAST_4, FIRST_LAST, FULL
-}
diff --git a/src/main/java/com/kingsrook/qbits/secureddata/standin/ProtectedAuditValues.java b/src/main/java/com/kingsrook/qbits/secureddata/standin/ProtectedAuditValues.java
deleted file mode 100644
index f795bfd..0000000
--- a/src/main/java/com/kingsrook/qbits/secureddata/standin/ProtectedAuditValues.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2026. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.kingsrook.qbits.secureddata.standin;
-
-
-/*******************************************************************************
- ** Tokens and a value-free message supplied before an audit is inserted.
- *******************************************************************************/
-// STAND-IN(core=QRun-IO/qqq#1182, removeBy=RC.8)
-public record ProtectedAuditValues(String oldToken, String newToken, String message)
-{
-}
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/CoreFieldAccessBridgeIT.java b/src/test/java/com/kingsrook/qbits/secureddata/CoreFieldAccessIT.java
similarity index 93%
rename from src/test/java/com/kingsrook/qbits/secureddata/CoreFieldAccessBridgeIT.java
rename to src/test/java/com/kingsrook/qbits/secureddata/CoreFieldAccessIT.java
index ab548fb..7297a1c 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/CoreFieldAccessBridgeIT.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/CoreFieldAccessIT.java
@@ -35,8 +35,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.channelUser;
@@ -55,34 +53,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
/*******************************************************************************
- ** Core field access bridge it.
+ ** Core field access integration tests.
*******************************************************************************/
-class CoreFieldAccessBridgeIT
+class CoreFieldAccessIT
{
static final String EMAIL = "CANARY-EMAIL-918273@example.invalid";
static final String SSN = "CANARY-PHI-192837465";
- /*******************************************************************************
- ** Require core.
- *******************************************************************************/
- @BeforeEach
- void requireCore()
- {
- boolean available;
- try
- {
- Class.forName("com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessResolverMetaData");
- available = true;
- }
- catch(ClassNotFoundException expected)
- {
- available = false;
- }
- Assumptions.assumeTrue(available, "QQQ field-access core (#1172) is not on this classpath");
- }
-
-
-
/*******************************************************************************
** Cleanup.
*******************************************************************************/
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/RetentionProofTest.java b/src/test/java/com/kingsrook/qbits/secureddata/RetentionProofTest.java
index 0e473b3..682a1d8 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/RetentionProofTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/RetentionProofTest.java
@@ -35,9 +35,9 @@ import java.util.Map;
import java.util.Optional;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
import com.kingsrook.qbits.secureddata.retention.WipeService;
-import com.kingsrook.qbits.secureddata.standin.ClassifiedValueKeySource.KeyAndId;
-import com.kingsrook.qbits.secureddata.standin.ClassifiedValueTokens;
import com.kingsrook.qbits.secureddata.support.SystemWrites;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueKeySource.KeyAndId;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
import com.kingsrook.qqq.backend.core.actions.reporting.ExportAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@@ -56,8 +56,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static com.kingsrook.qbits.secureddata.RetentionTestSupport.rows;
import static com.kingsrook.qbits.secureddata.RetentionTestSupport.rule;
@@ -67,7 +65,6 @@ import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** SECURED-DATA section 15 proofs: a planted canary never leaves the host table,
** and shredded audit tokens cannot be brute-forced once the data key is gone.
- ** Both need the core audit-protection hooks (#1182), so RC.6 skips them.
*******************************************************************************/
class RetentionProofTest
{
@@ -76,17 +73,6 @@ class RetentionProofTest
- /*******************************************************************************
- ** Skip unless core supplies the audit hooks.
- *******************************************************************************/
- @BeforeEach
- void requireCoreHooks()
- {
- Assumptions.assumeTrue(CoreAuditBridge.available(), "QQQ core audit-protection hooks (#1182) are not on this classpath");
- }
-
-
-
/*******************************************************************************
** Cleanup.
*******************************************************************************/
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/RetentionWipeTest.java b/src/test/java/com/kingsrook/qbits/secureddata/RetentionWipeTest.java
index fc5437b..dce0459 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/RetentionWipeTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/RetentionWipeTest.java
@@ -20,23 +20,23 @@
package com.kingsrook.qbits.secureddata;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.Base64;
import java.util.Set;
-import java.nio.charset.StandardCharsets;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
import com.kingsrook.qbits.secureddata.retention.RetentionRules;
import com.kingsrook.qbits.secureddata.retention.RetentionWidgets;
-import com.kingsrook.qbits.secureddata.retention.WipeService;
import com.kingsrook.qbits.secureddata.retention.WipeProcesses;
-import com.kingsrook.qbits.secureddata.standin.DMLType;
-import com.kingsrook.qbits.secureddata.standin.ClassifiedValueTokens;
+import com.kingsrook.qbits.secureddata.retention.WipeService;
import com.kingsrook.qbits.secureddata.support.SystemWrites;
+import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction.DMLType;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.ClassifiedValueTokens;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext;
@@ -50,8 +50,8 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static com.kingsrook.qbits.secureddata.RetentionTestSupport.*;
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/RevealAndBreakGlassTest.java b/src/test/java/com/kingsrook/qbits/secureddata/RevealAndBreakGlassTest.java
index 2da5f8f..86ec3c2 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/RevealAndBreakGlassTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/RevealAndBreakGlassTest.java
@@ -31,9 +31,9 @@ import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
import com.kingsrook.qbits.secureddata.processes.BreakGlassProcesses;
import com.kingsrook.qbits.secureddata.reveal.RevealCappedException;
import com.kingsrook.qbits.secureddata.reveal.RevealService;
-import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
import com.kingsrook.qbits.secureddata.support.SystemWrites;
+import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.alerts.AlertResolution;
import com.kingsrook.qqq.backend.core.alerts.AlertSeverity;
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLoggerTest.java b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLoggerTest.java
index 96ac9a2..9e5d1a3 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLoggerTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAccessLoggerTest.java
@@ -33,9 +33,9 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessEvent;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEvent;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome;
import com.kingsrook.qqq.backend.core.alerts.AlertResolution;
import com.kingsrook.qqq.backend.core.alerts.AlertSignal;
import com.kingsrook.qqq.backend.core.alerts.AlertSinkInterface;
@@ -49,7 +49,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
-
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.CLOCK;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.NOW;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.channelUser;
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAnomaliesTest.java b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAnomaliesTest.java
index 7075d90..776ccda 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAnomaliesTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataAnomaliesTest.java
@@ -25,16 +25,16 @@ import java.util.List;
import com.kingsrook.qbits.secureddata.breakglass.BreakGlassService;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
import com.kingsrook.qbits.secureddata.reveal.RevealService;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessEventType;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome;
import com.kingsrook.qbits.secureddata.support.ActivityLog;
+import com.kingsrook.qbits.secureddata.support.SystemWrites;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
-import com.kingsrook.qbits.secureddata.support.SystemWrites;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.*;
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataDdlTest.java b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataDdlTest.java
index e2bd08a..1218a31 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataDdlTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataDdlTest.java
@@ -33,7 +33,6 @@ import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaDat
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
-
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.CLOCK;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.RUNTIMES;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.base;
@@ -97,8 +96,8 @@ class SecuredDataDdlTest
classification(null, "PII");
policy("cs_admin", "PII", "REVEALABLE");
assertThat(SecuredDataRuntime.current().resolver().resolve(instance.getTable("person"), instance.getTable("person").getField("email")).tier().name()).isEqualTo("REVEALABLE");
- SecuredDataRuntime.current().logger().onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qbits.secureddata.standin.FieldAccessEventType.EXACT_MATCH_FILTER,
- com.kingsrook.qbits.secureddata.standin.FieldAccessOutcome.MATCH, "hmac"));
+ SecuredDataRuntime.current().logger().onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType.EXACT_MATCH_FILTER,
+ com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessOutcome.MATCH, "hmac"));
SecuredDataRuntime.current().logger().flush();
assertThat(logs()).extracting(row -> row.getValueString("event_type")).containsExactly("CLASSIFICATION_CHANGE", "POLICY_CHANGE", "EXACT_MATCH_FILTER");
// Equal timestamps must be counted by SQL as well as the memory backend.
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataHomeTest.java b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataHomeTest.java
index c43793d..acbec35 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataHomeTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataHomeTest.java
@@ -88,8 +88,8 @@ class SecuredDataHomeTest
delete(SecuredDataTables.POLICY, policy);
classification("email", "PII");
var logger = SecuredDataRuntime.current().logger();
- logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qbits.secureddata.standin.FieldAccessEventType.VIEW, null, null));
- logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qbits.secureddata.standin.FieldAccessEventType.VIEW, null, null));
+ logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType.VIEW, null, null));
+ logger.onAccess(SecuredDataAccessLoggerTest.event(com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessEventType.VIEW, null, null));
var data = SecuredDataHomeData.load();
assertThat(data.getKpis()).containsEntry("Reveals today", 15L).containsEntry("Active reveal grants", 1L)
.containsEntry("Active break-glass sessions", 1L).containsEntry("Anomaly flags (7 days)", 1L)
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducerTest.java b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducerTest.java
index c3f3eac..47a78ca 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducerTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataQBitProducerTest.java
@@ -26,11 +26,9 @@ import java.util.Map;
import com.kingsrook.qbits.secureddata.metadata.FieldsOfTablePossibleValueSource;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataEnums;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
-import com.kingsrook.qqq.backend.core.model.metadata.permissions.ClientChannelsMetaData;
-import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
-import com.kingsrook.qqq.backend.core.model.metadata.permissions.TableFeaturePermissionsMetaData;
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
+import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
@@ -40,11 +38,12 @@ import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSo
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.ClientChannelsMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.permissions.TableFeaturePermissionsMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
-
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.NOW;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.RUNTIMES;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.base;
@@ -89,7 +88,7 @@ class SecuredDataQBitProducerTest
{
QInstance instance = setup();
assertThat(instance.getTables().keySet()).containsAll(SecuredDataTables.ALL);
- assertThat(instance.getQBits().get("com.kingsrook.qbits:qbit-secured-data").getVersion()).isEqualTo("1.0.0");
+ assertThat(instance.getQBits().get("com.kingsrook.qbits:qbit-secured-data").getVersion()).isEqualTo("0.1.0");
for(String name : SecuredDataTables.ALL)
{
QTableMetaData table = instance.getTable(name);
diff --git a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataResolverTest.java b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataResolverTest.java
index c9910c5..be767dc 100644
--- a/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataResolverTest.java
+++ b/src/test/java/com/kingsrook/qbits/secureddata/SecuredDataResolverTest.java
@@ -23,12 +23,12 @@ package com.kingsrook.qbits.secureddata;
import java.util.List;
import com.kingsrook.qbits.secureddata.metadata.SecuredDataTables;
-import com.kingsrook.qqq.backend.core.actions.ai.AiTableMetaData;
-import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
-import com.kingsrook.qbits.secureddata.standin.FieldAccessTier;
-import com.kingsrook.qbits.secureddata.standin.FieldMaskStyle;
import com.kingsrook.qbits.secureddata.support.SecuredDataMetadata;
import com.kingsrook.qbits.secureddata.support.SystemWrites;
+import com.kingsrook.qqq.backend.core.actions.ai.AiTableMetaData;
+import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
+import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldMaskStyle;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@@ -39,7 +39,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
-
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.CLOCK;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.NOW;
import static com.kingsrook.qbits.secureddata.SecuredDataTestSupport.channelUser;